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 )