go-hep.org/x/hep@v0.38.1/groot/rtree/writer.go (about)

     1  // Copyright ©2019 The go-hep Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package rtree
     6  
     7  import (
     8  	"fmt"
     9  	"reflect"
    10  
    11  	"go-hep.org/x/hep/groot/internal/rcompress"
    12  	"go-hep.org/x/hep/groot/rbase"
    13  	"go-hep.org/x/hep/groot/riofs"
    14  	"go-hep.org/x/hep/groot/root"
    15  	"go-hep.org/x/hep/groot/rvers"
    16  )
    17  
    18  // Writer is the interface that wraps the Write method for Trees.
    19  type Writer interface {
    20  	Tree
    21  
    22  	// Write writes the event data to ROOT storage and returns the number
    23  	// of bytes (before compression, if any) written.
    24  	Write() (int, error)
    25  
    26  	// Flush commits the current contents of the tree to stable storage.
    27  	Flush() error
    28  
    29  	// Close writes metadata and closes the tree.
    30  	Close() error
    31  }
    32  
    33  // WriteOption configures how a ROOT tree (and its branches) should be created.
    34  type WriteOption func(opt *wopt) error
    35  
    36  type wopt struct {
    37  	title    string // title of the writer tree
    38  	bufsize  int32  // buffer size for branches
    39  	splitlvl int32  // maximum split-level for branches
    40  	compress int32  // compression algorithm name and compression level
    41  }
    42  
    43  // WithLZ4 configures a ROOT tree to use LZ4 as a compression mechanism.
    44  func WithLZ4(level int) WriteOption {
    45  	return func(opt *wopt) error {
    46  		opt.compress = rcompress.Settings{Alg: rcompress.LZ4, Lvl: level}.Compression()
    47  		return nil
    48  	}
    49  }
    50  
    51  // WithLZMA configures a ROOT tree to use LZMA as a compression mechanism.
    52  func WithLZMA(level int) WriteOption {
    53  	return func(opt *wopt) error {
    54  		opt.compress = rcompress.Settings{Alg: rcompress.LZMA, Lvl: level}.Compression()
    55  		return nil
    56  	}
    57  }
    58  
    59  // WithoutCompression configures a ROOT tree to not use any compression mechanism.
    60  func WithoutCompression() WriteOption {
    61  	return func(opt *wopt) error {
    62  		opt.compress = 0
    63  		return nil
    64  	}
    65  }
    66  
    67  // WithZlib configures a ROOT tree to use zlib as a compression mechanism.
    68  func WithZlib(level int) WriteOption {
    69  	return func(opt *wopt) error {
    70  		opt.compress = rcompress.Settings{Alg: rcompress.ZLIB, Lvl: level}.Compression()
    71  		return nil
    72  	}
    73  }
    74  
    75  // WithZstd configures a ROOT tree to use zstd as a compression mechanism.
    76  func WithZstd(level int) WriteOption {
    77  	return func(opt *wopt) error {
    78  		opt.compress = rcompress.Settings{Alg: rcompress.ZSTD, Lvl: level}.Compression()
    79  		return nil
    80  	}
    81  }
    82  
    83  // WithBasketSize configures a ROOT tree to use 'size' (in bytes) as a basket buffer size.
    84  // if size is <= 0, the default buffer size is used (DefaultBasketSize).
    85  func WithBasketSize(size int) WriteOption {
    86  	return func(opt *wopt) error {
    87  		if size <= 0 {
    88  			size = defaultBasketSize
    89  		}
    90  		opt.bufsize = int32(size)
    91  		return nil
    92  	}
    93  }
    94  
    95  // WithTitle sets the title of the tree writer.
    96  func WithTitle(title string) WriteOption {
    97  	return func(opt *wopt) error {
    98  		opt.title = title
    99  		return nil
   100  	}
   101  }
   102  
   103  // WithSplitLevel sets the maximum branch depth split level
   104  func WithSplitLevel(lvl int) WriteOption {
   105  	return func(opt *wopt) error {
   106  		opt.splitlvl = int32(lvl)
   107  		return nil
   108  	}
   109  }
   110  
   111  type wtree struct {
   112  	ttree
   113  	wvars []WriteVar
   114  
   115  	closed bool
   116  }
   117  
   118  // NewWriter creates a new Tree with the given name and under the given
   119  // directory dir, ready to be filled with data.
   120  func NewWriter(dir riofs.Directory, name string, vars []WriteVar, opts ...WriteOption) (Writer, error) {
   121  	if dir == nil {
   122  		return nil, fmt.Errorf("rtree: missing parent directory")
   123  	}
   124  
   125  	w := &wtree{
   126  		ttree: ttree{
   127  			f:         fileOf(dir),
   128  			dir:       dir,
   129  			rvers:     rvers.Tree,
   130  			named:     *rbase.NewNamed(name, ""),
   131  			attline:   *rbase.NewAttLine(),
   132  			attfill:   *rbase.NewAttFill(),
   133  			attmarker: *rbase.NewAttMarker(),
   134  			weight:    1,
   135  			scanField: 25,
   136  
   137  			defaultEntryOffsetLen: 1000,
   138  			maxEntries:            1000000000000,
   139  			maxEntryLoop:          1000000000000,
   140  			autoSave:              -300000000,
   141  			autoFlush:             -30000000,
   142  			estimate:              1000000,
   143  		},
   144  		wvars: vars,
   145  	}
   146  
   147  	cfg := wopt{
   148  		bufsize:  defaultBasketSize,
   149  		splitlvl: defaultSplitLevel,
   150  		compress: w.ttree.f.Compression(),
   151  	}
   152  
   153  	for _, opt := range opts {
   154  		err := opt(&cfg)
   155  		if err != nil {
   156  			return nil, fmt.Errorf("rtree: could not configure tree writer: %w", err)
   157  		}
   158  	}
   159  
   160  	w.ttree.named.SetTitle(cfg.title)
   161  
   162  	for _, v := range vars {
   163  		b, err := newBranchFromWVar(w, v.Name, v, nil, 0, cfg)
   164  		if err != nil {
   165  			return nil, fmt.Errorf("rtree: could not create branch for write-var %#v: %w", v, err)
   166  		}
   167  		w.ttree.branches = append(w.ttree.branches, b)
   168  	}
   169  
   170  	return w, nil
   171  }
   172  
   173  func (w *wtree) SetTitle(title string) { w.ttree.named.SetTitle(title) }
   174  
   175  func (w *wtree) ROOTMerge(src root.Object) error {
   176  	switch src := src.(type) {
   177  	case Tree:
   178  		r, err := NewReader(src, nil)
   179  		if err != nil {
   180  			return fmt.Errorf("rtree: could not create tree reader: %w", err)
   181  		}
   182  		defer r.Close()
   183  
   184  		_, err = Copy(w, r)
   185  		if err != nil {
   186  			return fmt.Errorf("rtree: could not merge tree: %w", err)
   187  		}
   188  		return nil
   189  	default:
   190  		return fmt.Errorf("rtree: can not merge src=%T into dst=%T", src, w)
   191  	}
   192  }
   193  
   194  // Write writes the event data to ROOT storage and returns the number
   195  // of bytes (before compression, if any) written.
   196  func (w *wtree) Write() (int, error) {
   197  	var (
   198  		tot int
   199  		zip int
   200  	)
   201  	for _, b := range w.ttree.branches {
   202  		nbytes, err := b.write()
   203  		if err != nil {
   204  			return tot, fmt.Errorf("rtree: could not write branch %q: %w", b.Name(), err)
   205  		}
   206  		tot += nbytes
   207  	}
   208  	w.ttree.entries++
   209  	w.ttree.totBytes += int64(tot)
   210  	w.ttree.zipBytes += int64(zip)
   211  	// FIXME(sbinet): autoflush
   212  
   213  	return tot, nil
   214  }
   215  
   216  // Flush commits the current contents of the tree to stable storage.
   217  func (w *wtree) Flush() error {
   218  	for _, b := range w.ttree.branches {
   219  		err := b.flush()
   220  		if err != nil {
   221  			return fmt.Errorf("rtree: could not flush branch %q: %w", b.Name(), err)
   222  		}
   223  	}
   224  	return nil
   225  }
   226  
   227  // Close writes metadata and closes the tree.
   228  func (w *wtree) Close() error {
   229  	if w.closed {
   230  		return nil
   231  	}
   232  	defer func() {
   233  		w.closed = true
   234  	}()
   235  
   236  	if err := w.Flush(); err != nil {
   237  		return fmt.Errorf("rtree: could not flush tree %q: %w", w.Name(), err)
   238  	}
   239  
   240  	if err := w.ttree.dir.Put(w.Name(), w); err != nil {
   241  		return fmt.Errorf("rtree: could not save tree %q: %w", w.Name(), err)
   242  	}
   243  
   244  	return nil
   245  }
   246  
   247  func fileOf(d riofs.Directory) *riofs.File {
   248  	const max = 1<<31 - 1
   249  	for range max {
   250  		p := d.Parent()
   251  		if p == nil {
   252  			return d.(*riofs.File)
   253  		}
   254  		d = p
   255  	}
   256  	panic("impossible")
   257  }
   258  
   259  func flattenArrayType(rt reflect.Type) (reflect.Type, []int) {
   260  	var (
   261  		shape []int
   262  		kind  = rt.Kind()
   263  	)
   264  	const max = 1<<31 - 1
   265  	for range max {
   266  		if kind != reflect.Array {
   267  			return rt, shape
   268  		}
   269  		shape = append(shape, rt.Len())
   270  		rt = rt.Elem()
   271  		kind = rt.Kind()
   272  	}
   273  	panic("impossible")
   274  }
   275  
   276  var (
   277  	_ Tree        = (*wtree)(nil)
   278  	_ Writer      = (*wtree)(nil)
   279  	_ root.Merger = (*wtree)(nil)
   280  )