The Gaudi Framework  master (2e57474e)
Loading...
Searching...
No Matches
RCWNTupleCnv.cpp
Go to the documentation of this file.
1/***********************************************************************************\
2* (c) Copyright 1998-2026 CERN for the benefit of the LHCb and ATLAS collaborations *
3* *
4* This software is distributed under the terms of the Apache version 2 licence, *
5* copied verbatim in the file "LICENSE". *
6* *
7* In applying this licence, CERN does not waive the privileges and immunities *
8* granted to it by virtue of its status as an Intergovernmental Organization *
9* or submit itself to any jurisdiction. *
10\***********************************************************************************/
11#define ROOTHISTCNV_RCWNTUPLECNV_CPP
12
13// Include files
18#include <GaudiKernel/NTuple.h>
19
20// Compiler include files
21#include <algorithm>
22#include <chrono>
23#include <cstdio>
24#include <cstring>
25#include <filesystem>
26#include <list>
27#include <numeric>
28#include <set>
29#include <span>
30#include <sstream>
31#include <type_traits>
32#include <utility>
33#include <vector>
34
35#include "RCWNTupleCnv.h"
36
37#include <fmt/format.h>
38
39#include <TLeafD.h>
40#include <TLeafF.h>
41#include <TLeafI.h>
42#include <TTree.h>
43#include <TUrl.h>
44
45namespace {
46 namespace fs = std::filesystem;
47 namespace DiskBuffer = Gaudi::NTuple::DiskBuffer;
49
50 template <typename T>
51 size_t saveItem( char* target, const NTuple::_Data<T>& src ) {
52 static_assert( std::is_trivially_copyable_v<T>, "T must be trivally copyable" );
53 std::memcpy( target, src.buffer(), sizeof( T ) * src.length() );
54 return sizeof( T ) * src.length();
55 }
56
57 template <typename T>
58 size_t loadItem( const char* src, NTuple::_Data<T>& target ) {
59 static_assert( std::is_trivially_copyable_v<T>, "T must be trivally copyable" );
60 std::memcpy( const_cast<void*>( target.buffer() ), src, sizeof( T ) * target.length() );
61 return sizeof( T ) * target.length();
62 }
63
64 template <typename POD>
65 decltype( auto ) downcast_item( const INTupleItem& i ) {
66 return dynamic_cast<const NTuple::_Data<POD>&>( i );
67 }
68 template <typename POD>
69 decltype( auto ) downcast_item( INTupleItem& i ) {
70 return dynamic_cast<NTuple::_Data<POD>&>( i );
71 }
72 template <typename POD, typename T>
73 void downcast_item( T&& ) = delete;
74
75 template <typename Item, typename F>
76 decltype( auto ) visit( Item& i, F&& f ) {
77 switch ( i.type() ) {
79 return f( downcast_item<int>( i ) );
81 return f( downcast_item<char>( i ) );
83 return f( downcast_item<short>( i ) );
85 return f( downcast_item<long>( i ) );
87 return f( downcast_item<long long>( i ) );
89 return f( downcast_item<unsigned char>( i ) );
91 return f( downcast_item<unsigned short>( i ) );
93 return f( downcast_item<unsigned int>( i ) );
95 return f( downcast_item<unsigned long>( i ) );
97 return f( downcast_item<unsigned long long>( i ) );
99 return f( downcast_item<double>( i ) );
101 return f( downcast_item<float>( i ) );
103 return f( downcast_item<bool>( i ) );
104 }
105 throw std::runtime_error( "RCWNTupleCnv::visit: unknown INTupleItem::type()" );
106 }
107
108 //-----------------------------------------------------------------------------
109 template <class T>
110 void analyzeItem( const std::string& typ, const NTuple::_Data<T>* it, std::string& desc, std::string& block_name,
111 std::string& var_name, long& lowerRange, long& upperRange, long& size )
112 //-----------------------------------------------------------------------------
113 {
114
115 RootHistCnv::parseName( it->name(), block_name, var_name );
116
117 // long item_size = (sizeof(T) < 4) ? 4 : sizeof(T);
118 long item_size = sizeof( T );
119 long dimension = it->length();
120 long ndim = it->ndim() - 1;
121 desc += var_name;
122 if ( it->hasIndex() || it->length() > 1 ) { desc += '['; }
123 if ( it->hasIndex() ) {
124 std::string ind_blk, ind_var;
125 RootHistCnv::parseName( it->index(), ind_blk, ind_var );
126 if ( ind_blk != block_name ) {
127 std::cerr << "ERROR: Index for CWNT variable " << ind_var << " is in a different block: " << ind_blk
128 << std::endl;
129 }
130 desc += ind_var;
131 } else if ( it->dim( ndim ) > 1 ) {
132 desc += std::to_string( it->dim( ndim ) );
133 }
134
135 for ( int i = ndim - 1; i >= 0; i-- ) {
136 desc += "][";
137 desc += std::to_string( it->dim( i ) );
138 }
139 if ( it->hasIndex() || it->length() > 1 ) { desc += ']'; }
140
141 // 0 and -1 are used to mark that the range is not defined
142 lowerRange = 0;
143 upperRange = -1;
144 if constexpr ( std::is_integral_v<T> ) {
145 // An explicit range makes sense only for integral types so we check only in that case if it is defined.
146 // Note that later a range is only taken into account for int32_t.
147 if ( it->range().lower() != it->range().min() && it->range().upper() != it->range().max() ) {
148 lowerRange = it->range().lower();
149 upperRange = it->range().upper();
150 }
151 }
152
153 desc += typ;
154 size += item_size * dimension;
155 }
156
157 // --- disk buffer helpers ----------------------------------------------------
158
160 template <typename T>
161 long indexValue( const NTuple::_Data<T>&, const void* p ) {
162 T v;
163 std::memcpy( &v, p, sizeof( T ) );
164 return static_cast<long>( v );
165 }
166 long indexValue( const INTupleItem& index, const void* p ) {
167 return visit( index, [p]( const auto& d ) { return indexValue( d, p ); } );
168 }
169
171 long usedUnits( const Layout::Item& li, const INTupleItem& index, const void* indexData ) {
172 return li.indexPos < 0 ? li.maxUnits : std::clamp( indexValue( index, indexData ), 0L, li.maxUnits );
173 }
174
176 void packRow( DiskBuffer::Writer& w, const Layout& layout, const INTuple::ItemContainer& items ) {
177 w.beginRecord();
178 for ( size_t k = 0; k < items.size(); ++k ) {
179 const auto& li = layout.items[k];
180 const long n = li.indexPos < 0 ? li.maxUnits : usedUnits( li, *items[li.indexPos], items[li.indexPos]->buffer() );
181 w.append( items[k]->buffer(), n * li.unitBytes );
182 }
183 w.endRecord();
184 }
185
187 void unpackRow( std::span<const char> rec, const Layout& layout, const INTuple::ItemContainer& items, char* buf,
188 const char* defaults ) {
189 const char* src = rec.data();
190 std::size_t left = rec.size();
191 for ( size_t k = 0; k < items.size(); ++k ) {
192 const auto& li = layout.items[k];
193 // an index always precedes the items it counts, so its slot is already unpacked
194 const long n =
195 li.indexPos < 0 ? li.maxUnits : usedUnits( li, *items[li.indexPos], buf + layout.items[li.indexPos].bufPos );
196 const std::size_t bytes = n * li.unitBytes;
197 if ( bytes > left ) throw std::runtime_error( "record shorter than its layout" );
198 std::memcpy( buf + li.bufPos, src, bytes );
199 std::memcpy( buf + li.bufPos + bytes, defaults + li.bufPos + bytes, li.bufLen - bytes );
200 src += bytes;
201 left -= bytes;
202 }
203 if ( left != 0 ) throw std::runtime_error( "record longer than its layout" );
204 }
205
207 std::string describe( const std::string& id, const INTuple& nt, const Layout& layout ) {
208 std::ostringstream os;
209 os << "id " << id << "\ntitle " << nt.title() << "\nrowbytes " << layout.rowBytes << "\nitems "
210 << layout.items.size() << "\n# block name leaflist type bufPos bufLen unitBytes indexPos\n";
211 for ( size_t k = 0; k < layout.items.size(); ++k ) {
212 const auto& li = layout.items[k];
213 os << li.block << ' ' << li.name << ' ' << li.leaflist << ' ' << nt.items()[k]->type() << ' ' << li.bufPos << ' '
214 << li.bufLen << ' ' << li.unitBytes << ' ' << li.indexPos << '\n';
215 }
216 return os.str();
217 }
218
219 // buffer directories in use by any output stream of this process
220 std::mutex s_dirsMutex;
221 std::set<fs::path> s_claimedDirs;
222} // namespace
223
224//-----------------------------------------------------------------------------
226//-----------------------------------------------------------------------------
227{
228 MsgStream log( msgSvc(), "RCWNTupleCnv" );
229 Layout layout;
230 const auto& items = nt.items();
231 layout.items.reserve( items.size() );
232 long size = 0;
233
234 for ( const auto& i : items ) {
235 Layout::Item li;
236 const long oldsize = size;
237
238 visit( *i, [&]( const auto& data ) {
239 analyzeItem( this->rootVarType( data.type() ), &data, li.leaflist, li.block, li.name, li.rangeLower,
240 li.rangeUpper, size );
241 } );
242
243 li.bufPos = oldsize;
244 li.bufLen = size - oldsize;
245 if ( i->hasIndex() ) {
246 auto idx = std::find( items.begin(), items.end(), i->indexItem() );
247 li.indexPos = idx != items.end() ? idx - items.begin() : -1;
248 }
249 const long elemBytes = i->length() > 0 ? i->size() / i->length() : 0;
250 li.unitBytes = elemBytes * ( i->ndim() == 2 ? i->dim( 0 ) : 1 ); // a matrix is counted in columns
251 li.maxUnits = li.unitBytes > 0 ? li.bufLen / li.unitBytes : 0;
252
253 log << MSG::VERBOSE << "item: " << li.leaflist << " type " << i->type() << " blk: " << li.block
254 << " var: " << li.name << " rng: " << li.rangeLower << " " << li.rangeUpper << " sz: " << size << " "
255 << li.bufLen << " buf_pos: " << li.bufPos << endmsg;
256
257 layout.items.push_back( std::move( li ) );
258 }
259 layout.rowBytes = size;
260 return layout;
261}
262
263//-----------------------------------------------------------------------------
264StatusCode RootHistCnv::RCWNTupleCnv::createTree( const std::string& desc, INTuple* nt, const Layout& layout,
265 TTree*& rtree )
266//-----------------------------------------------------------------------------
267{
268 MsgStream log( msgSvc(), "RCWNTupleCnv" );
269 rtree = new TTree( desc.c_str(), nt->title().c_str() );
270 log << MSG::VERBOSE << "created tree id: " << rtree->GetName() << " title: " << nt->title() << " desc: " << desc
271 << endmsg;
272
273 // Make a new buffer, and tell the ntuple where it is
274 const long size = layout.rowBytes;
275 char* buff = nt->setBuffer( new char[size] );
276
277 log << MSG::VERBOSE << "Created buffer size: " << size << " at " << (void*)buff << endmsg;
278
279 // Zero out the buffer to make ROOT happy
280 std::fill_n( buff, size, 0 );
281
282 Gaudi::Property<int> basket_size( "BasketSize", 32000 );
283 m_ntupleSvc.as<IProperty>()->getProperty( &basket_size ).ignore();
284
285 // Loop over items, creating a new branch for each one;
286 for ( const auto& li : layout.items ) {
287
288 char* buf_pos = buff + li.bufPos;
289
290 auto br = new TBranch( rtree, li.name.c_str(), buf_pos, li.leaflist.c_str(), basket_size );
291 if ( li.block != "AUTO_BLK" ) {
292 std::string title = li.block + "::" + br->GetTitle();
293 br->SetTitle( title.c_str() );
294 }
295
296 log << MSG::DEBUG << "adding TBranch " << br->GetTitle() << " at " << (void*)buf_pos << endmsg;
297
298 // for index items with a limited range. Must be a TLeafI!
299 if ( li.rangeLower < li.rangeUpper ) {
300 TLeafI* index = nullptr;
301 TObject* tobj = br->GetListOfLeaves()->FindObject( li.name.c_str() );
302 if ( tobj->IsA()->InheritsFrom( "TLeafI" ) ) {
303 index = dynamic_cast<TLeafI*>( tobj );
304
305 if ( index ) {
306 index->SetMaximum( li.rangeUpper );
307 // FIXME -- add for next version of ROOT
308 // index->SetMinimum( li.rangeLower );
309 } else {
310 log << MSG::ERROR << "Could dynamic cast to TLeafI: " << li.name << endmsg;
311 }
312 }
313 }
314
315 rtree->GetListOfBranches()->Add( br );
316 }
317
318 return StatusCode::SUCCESS;
319}
320
321//-----------------------------------------------------------------------------
323//-----------------------------------------------------------------------------
324{
326 if ( !sc ) return sc;
327 MsgStream log( msgSvc(), "RCWNTupleCnv" );
328
329 // the DiskBuffer properties belong to the tuple service owning this stream, which is our data provider
331 if ( !owner ) owner = m_ntupleSvc.as<IProperty>(); // not owned by a tuple service: keep the lookup by name
332 Gaudi::Property<bool> diskBuffer( "DiskBuffer", false );
333 Gaudi::Property<std::string> directory( "DiskBufferDirectory", "" );
334 Gaudi::Property<int> level( "DiskBufferCompression", 3 );
335 Gaudi::Property<int> blockSize( "DiskBufferBlockSize", 64 * 1024 );
336 if ( owner ) {
337 for ( auto* p :
338 std::initializer_list<Gaudi::Details::PropertyBase*>{ &diskBuffer, &directory, &level, &blockSize } )
339 owner->getProperty( p ).ignore();
340 }
341
342 m_diskBuffer = diskBuffer;
344 m_diskBufferLevel = level;
345 m_diskBufferBlockSize = static_cast<std::size_t>( blockSize.value() );
346 if ( !m_diskBuffer ) return sc;
347
348 if ( m_diskBufferLevel != 0 && !DiskBuffer::zstdAvailable() ) {
349 log << MSG::WARNING << "DiskBuffer: zstd not available in this build, not compressing" << endmsg;
351 }
352 if ( blockSize <= 0 || m_diskBufferBlockSize > DiskBuffer::maxBlockSize() ) {
353 log << MSG::ERROR << "DiskBuffer: DiskBufferBlockSize must be between 1 and " << DiskBuffer::maxBlockSize()
354 << endmsg;
355 return StatusCode::FAILURE;
356 }
357 return sc;
358}
359
360//-----------------------------------------------------------------------------
361StatusCode RootHistCnv::RCWNTupleCnv::book( const std::string& desc, INTuple* nt, TTree*& rtree )
362//-----------------------------------------------------------------------------
363{
364 rtree = nullptr;
365 Layout layout = analyse( *nt );
366
367 if ( m_diskBuffer && !m_bookDirect ) return bookBuffered( desc, nt, std::move( layout ) );
368
369 StatusCode sc = createTree( desc, nt, layout, rtree );
370 if ( sc ) {
371 MsgStream log( msgSvc(), "RCWNTupleCnv" );
372 log << MSG::INFO << "Booked TTree with ID: " << desc << " \"" << nt->title() << "\" in directory " << getDirectory()
373 << endmsg;
374 }
375 return sc;
376}
377
378//-----------------------------------------------------------------------------
380//-----------------------------------------------------------------------------
381{
382 if ( !m_bufferDir.empty() ) {
383 dir = m_bufferDir;
384 return StatusCode::SUCCESS;
385 }
386 MsgStream log( msgSvc(), "RCWNTupleCnv" );
387
388 TFile* file = gDirectory ? gDirectory->GetFile() : nullptr;
389 if ( !file ) {
390 log << MSG::ERROR << "DiskBuffer: " << getDirectory() << " is not inside a file" << endmsg;
391 return StatusCode::FAILURE;
392 }
393 TUrl url( file->GetName(), kTRUE );
394 fs::path base;
395 if ( m_diskBufferDirectory.empty() && std::string_view( url.GetProtocol() ) == "file" ) {
396 base = url.GetFile();
397 } else {
398 base =
399 fs::path( m_diskBufferDirectory.empty() ? "." : m_diskBufferDirectory ) / fs::path( url.GetFile() ).filename();
400 }
401 base += ".ntbuf";
402
403 {
404 std::lock_guard lock( s_dirsMutex );
405 if ( !s_claimedDirs.insert( base ).second ) {
406 log << MSG::ERROR << "DiskBuffer directory " << base << " is already used by another output stream" << endmsg;
407 return StatusCode::FAILURE;
408 }
409 }
410 std::error_code ec;
411 if ( fs::exists( base, ec ) ) {
412 log << MSG::WARNING << "Removing stale DiskBuffer directory " << base << endmsg;
413 fs::remove_all( base, ec );
414 }
415 if ( !ec ) fs::create_directories( base, ec );
416 if ( ec ) {
417 log << MSG::ERROR << "Cannot create DiskBuffer directory " << base << ": " << ec.message() << endmsg;
418 std::lock_guard lock( s_dirsMutex );
419 s_claimedDirs.erase( base );
420 return StatusCode::FAILURE;
421 }
422 m_bufferDir = base;
423 dir = base;
424 return StatusCode::SUCCESS;
425}
426
427//-----------------------------------------------------------------------------
429//-----------------------------------------------------------------------------
430{
431 MsgStream log( msgSvc(), "RCWNTupleCnv" );
432 const auto& items = nt->items();
433 for ( size_t k = 0; k < items.size(); ++k ) {
434 const auto& li = layout.items[k];
435 if ( items[k]->hasIndex() && ( li.indexPos < 0 || li.indexPos >= long( k ) ) ) {
436 log << MSG::ERROR << "DiskBuffer: index " << items[k]->index() << " must be added before " << items[k]->name()
437 << " in " << desc << endmsg;
438 return StatusCode::FAILURE;
439 }
440 }
441 std::string id = desc;
442 if ( auto obj = dynamic_cast<DataObject*>( nt ); obj && obj->registry() ) id = obj->registry()->identifier();
443
444 std::lock_guard lock( m_mutex );
445 fs::path dir;
446 if ( auto sc = bufferDirectory( dir ); !sc ) return sc;
447 const fs::path path = dir / fmt::format( "{:04}.ntbuf", m_nextBufferId++ );
448
449 Buffered b{ desc, std::move( layout ), nullptr };
450 try {
451 b.writer = std::make_unique<DiskBuffer::Writer>( path, describe( id, *nt, b.layout ), m_diskBufferLevel,
453 } catch ( const DiskBuffer::Error& e ) {
454 log << MSG::ERROR << e.what() << endmsg;
455 return StatusCode::FAILURE;
456 }
457 log << MSG::INFO << "Buffering TTree with ID: " << desc << " \"" << nt->title() << "\" in directory "
458 << getDirectory() << " to " << path.string() << endmsg;
459 m_buffered.emplace( nt, std::move( b ) );
460 return StatusCode::SUCCESS;
461}
462
463//-----------------------------------------------------------------------------
465//-----------------------------------------------------------------------------
466{
467 std::lock_guard lock( m_mutex );
468 auto it = m_buffered.find( nt );
469 return it != m_buffered.end() ? &it->second : nullptr;
470}
471
472//-----------------------------------------------------------------------------
474//-----------------------------------------------------------------------------
475{
476 std::lock_guard lock( m_mutex );
477 return m_replayed.count( nt ) != 0;
478}
479
480// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
481
482//-----------------------------------------------------------------------------
484//-----------------------------------------------------------------------------
485{
486 if ( !rtree ) {
487 if ( Buffered* b = buffered( nt ) ) return writeBuffered( *b, nt );
488 MsgStream log( msgSvc(), "RCWNTupleCnv" );
489 if ( replayed( nt ) ) {
490 // save() built the tree and closed the buffer; there is nowhere to put this row
491 log << MSG::ERROR << "N-tuple \"" << nt->title()
492 << "\" was already written from its disk buffer: with NTupleSvc.DiskBuffer a tuple cannot be filled after "
493 "it has been saved. Row lost."
494 << endmsg;
495 } else {
496 log << MSG::ERROR << "No TTree and no disk buffer for N-tuple \"" << nt->title() << "\"" << endmsg;
497 }
498 return StatusCode::FAILURE;
499 }
500
501 // Fill the tree;
502 const auto& items = nt->items();
503 std::accumulate( begin( items ), end( items ), nt->buffer(), []( char* dest, const INTupleItem* i ) {
504 return dest + visit( *i, [dest]( const auto& item ) { return saveItem( dest, item ); } );
505 } );
506
507 rtree->Fill();
508 nt->reset();
509 return StatusCode::SUCCESS;
510}
511
512//-----------------------------------------------------------------------------
514//-----------------------------------------------------------------------------
515{
516 const auto& items = nt->items();
517 if ( items.size() != b.layout.items.size() ) {
518 MsgStream log( msgSvc(), "RCWNTupleCnv" );
519 log << MSG::ERROR << "N-tuple " << b.desc << " changed shape after its first write" << endmsg;
520 return StatusCode::FAILURE;
521 }
522 try {
523 packRow( *b.writer, b.layout, items );
524 } catch ( const DiskBuffer::Error& e ) {
525 MsgStream log( msgSvc(), "RCWNTupleCnv" );
526 log << MSG::ERROR << "Disk buffer write failed: " << e.what() << endmsg;
527 return StatusCode::FAILURE;
528 }
529 nt->reset();
530 return StatusCode::SUCCESS;
531}
532
533//-----------------------------------------------------------------------------
535//-----------------------------------------------------------------------------
536{
537 if ( !pAddr ) {
538 // never written: the base class books it and writes one default row; keep that on the direct path
539 m_bookDirect = true;
540 StatusCode sc = RNTupleCnv::updateRep( pAddr, pObj );
541 m_bookDirect = false;
542 return sc;
543 }
544 INTuple* nt = dynamic_cast<INTuple*>( pObj );
545 Buffered* b = nt ? buffered( nt ) : nullptr;
546 if ( b ) return replay( pAddr, nt, *b );
547 // an earlier save() already built and wrote the tree, there is nothing left to do
548 if ( nt && replayed( nt ) ) return StatusCode::SUCCESS;
549 return RNTupleCnv::updateRep( pAddr, pObj );
550}
551
552//-----------------------------------------------------------------------------
554//-----------------------------------------------------------------------------
555{
556 MsgStream log( msgSvc(), "RCWNTupleCnv" );
558 const auto t0 = std::chrono::steady_clock::now();
559 const std::string desc = b.desc;
560 const fs::path path = b.writer->path();
561 TDirectory* pDir = (TDirectory*)pAddr->ipar()[0];
562 TTree* tree = nullptr;
564 std::uint64_t entries = 0, bytes = 0, stored = 0;
565
566 try {
567 b.writer->close();
568 entries = b.writer->entries();
569 bytes = b.writer->bytes();
570 stored = b.writer->storedBytes();
571 b.writer.reset(); // free its write cache before the tree is built
572 if ( nt->items().size() != b.layout.items.size() )
573 throw std::runtime_error( "N-tuple changed shape after its first write" );
574 if ( !pDir ) throw std::runtime_error( "no output directory" );
575 pDir->cd();
576 sc = createTree( desc, nt, b.layout, tree );
577 if ( sc ) {
578 const auto& items = nt->items();
579 char* buf = nt->buffer();
580 // the part of a slot not stored in a record is whatever reset() leaves there
581 nt->reset();
582 std::vector<char> defaults( b.layout.rowBytes );
583 for ( size_t k = 0; k < items.size(); ++k )
584 std::memcpy( defaults.data() + b.layout.items[k].bufPos, items[k]->buffer(), b.layout.items[k].bufLen );
585
586 DiskBuffer::Reader reader( path );
587 std::span<const char> rec;
588 while ( reader.next( rec ) ) {
589 unpackRow( rec, b.layout, items, buf, defaults.data() );
590 tree->Fill();
591 }
592 if ( reader.entries() != entries )
593 throw std::runtime_error( fmt::format( "expected {} entries, found {}", entries, reader.entries() ) );
594 if ( tree->Write( "", TObject::kOverwrite ) == 0 ) throw std::runtime_error( "TTree::Write failed" );
595 }
596 } catch ( const std::exception& e ) {
597 log << MSG::ERROR << "Failed to build TTree " << desc << " from disk buffer " << path.string() << ": " << e.what()
598 << endmsg;
600 }
601 delete tree;
602 nt->setBuffer( nullptr );
603
604 std::error_code ec;
605 if ( sc ) fs::remove( path, ec );
606 {
607 std::lock_guard lock( m_mutex );
608 m_buffered.erase( nt );
609 m_replayed.insert( nt );
610 if ( m_buffered.empty() ) {
611 fs::remove( m_bufferDir, ec ); // only when empty, i.e. nothing was left behind for inspection
613 }
614 }
615 if ( sc ) {
616 const double secs = std::chrono::duration<double>( std::chrono::steady_clock::now() - t0 ).count();
617 log << MSG::INFO << "Built TTree with ID: " << desc << " \"" << nt->title() << "\" in directory " << pDir->GetPath()
618 << " from disk buffer: " << entries << " entries, " << bytes << " bytes (" << stored << " on disk), "
619 << fmt::format( "{:.2f}", secs ) << " s" << endmsg;
620 }
621 return sc;
622}
623
624//-----------------------------------------------------------------------------
626//-----------------------------------------------------------------------------
627{
628 if ( m_bufferDir.empty() ) return;
629 std::lock_guard lock( s_dirsMutex );
630 s_claimedDirs.erase( m_bufferDir );
631 m_bufferDir.clear();
632}
633
634//-----------------------------------------------------------------------------
636//-----------------------------------------------------------------------------
637{
638 {
639 std::lock_guard lock( m_mutex );
640 if ( !m_buffered.empty() ) {
641 MsgStream log( msgSvc(), "RCWNTupleCnv" );
642 for ( auto& [nt, b] : m_buffered ) {
643 // flush first, so the file left behind holds every row that was written
644 try {
645 b.writer->close();
646 } catch ( const DiskBuffer::Error& e ) { log << MSG::ERROR << e.what() << endmsg; }
647 log << MSG::ERROR << "TTree " << b.desc << " was never built from its disk buffer " << b.writer->path().string()
648 << endmsg;
649 }
650 m_buffered.clear();
651 }
652 m_replayed.clear();
654 }
655 return RNTupleCnv::finalize();
656}
657
658//-----------------------------------------------------------------------------
660//-----------------------------------------------------------------------------
661{
662 if ( !rtree ) {
663 MsgStream log( msgSvc(), "RCWNTupleCnv::readData" );
664 log << MSG::ERROR << "cannot read a disk-buffered N-tuple: its TTree is only built at finalize" << endmsg;
665 return StatusCode::FAILURE;
666 }
667 if ( ievt >= rtree->GetEntries() ) {
668 MsgStream log( msgSvc(), "RCWNTupleCnv::readData" );
669 log << MSG::ERROR << "no more entries in tree to read. max: " << rtree->GetEntries() << " current: " << ievt
670 << endmsg;
671 return StatusCode::FAILURE;
672 }
673
674 rtree->GetEvent( ievt );
675 ievt++;
676
677 // copy data from ntup->buffer() to ntup->items()->buffer()
678 auto& items = ntup->items();
679 std::accumulate( begin( items ), end( items ), const_cast<const char*>( ntup->buffer() ),
680 []( const char* src, INTupleItem* i ) {
681 return src + visit( *i, [src]( auto& item ) { return loadItem( src, item ); } );
682 } );
683
684 return StatusCode::SUCCESS;
685}
686
687//-----------------------------------------------------------------------------
689//-----------------------------------------------------------------------------
690{
691 MsgStream log( msgSvc(), "RCWNTupleCnv::load" );
692
693 StatusCode status;
694
695 NTuple::Tuple* pObj = nullptr;
696
697 std::string title = tree->GetTitle();
698 log << MSG::VERBOSE << "loading CWNT " << title << " at: " << tree << endmsg;
699
700 status = m_ntupleSvc->create( CLID_ColumnWiseTuple, title, pObj );
701 INTuple* ntup = dynamic_cast<INTuple*>( pObj );
702 if ( !ntup ) { log << MSG::ERROR << "cannot dynamic cast to INTuple" << endmsg; }
703
704 INTupleItem* item = nullptr;
705
706 std::string itemName, indexName, item_type, itemTitle, blockName;
707 // long numEnt, numVar;
708 long size, totsize = 0;
709 std::vector<std::pair<TLeaf*, int>> itemList;
710
711 // numEnt = (int)tree->GetEntries();
712 // numVar = tree->GetNbranches();
713
714 // loop over all branches (==leaves)
715 TObjArray* lbr = tree->GetListOfBranches();
716 TIter bitr( lbr );
717 while ( TObject* tobjb = bitr() ) {
718
719 TBranch* br = dynamic_cast<TBranch*>( tobjb );
720 itemTitle = br->GetTitle();
721
722 int ipos = itemTitle.find( "::" );
723 if ( ipos >= 0 ) {
724 blockName = itemTitle.substr( 0, ipos );
725 } else {
726 blockName = "";
727 }
728
729 TObjArray* lf = br->GetListOfLeaves();
730
731 TIter litr( lf );
732 while ( TObject* tobj = litr() ) {
733
734 bool hasRange = false;
735 int indexRange = 0;
736 int itemSize;
737 item = nullptr;
738
739 TLeaf* tl = dynamic_cast<TLeaf*>( tobj );
740 if ( !tl ) {
741 log << MSG::ERROR << "cannot dynamic cast to TLeaf" << endmsg;
742 return StatusCode::FAILURE;
743 }
744 itemName = tl->GetName();
745
746 if ( blockName != "" ) {
747 log << MSG::DEBUG << "loading NTuple item " << blockName << "/" << itemName;
748 } else {
749 log << MSG::DEBUG << "loading NTuple item " << itemName;
750 }
751
752 int arraySize{ 0 };
753 TLeaf* indexLeaf = tl->GetLeafCounter( arraySize );
754
755 if ( arraySize == 0 ) { log << MSG::ERROR << "TLeaf counter size = 0. This should not happen!" << endmsg; }
756
757 if ( indexLeaf ) {
758 // index Arrays and Matrices
759
760 indexName = indexLeaf->GetName();
761 indexRange = indexLeaf->GetMaximum();
762 itemSize = indexRange * tl->GetLenType() * arraySize;
763
764 log << "[" << indexName;
765
766 // Just for Matrices
767 if ( arraySize != 1 ) { log << "][" << arraySize; }
768 log << "]";
769
770 } else {
771 itemSize = tl->GetLenType() * arraySize;
772
773 indexName = "";
774
775 if ( arraySize == 1 ) {
776 // Simple items
777 } else {
778 // Arrays of constant size
779 log << "[" << arraySize << "]";
780 }
781 }
782
783 log << endmsg;
784
785 size = itemSize;
786 totsize += size;
787
788 hasRange = tl->IsRange();
789
790 itemList.emplace_back( tl, itemSize );
791
792 // Integer
793 if ( tobj->IsA()->InheritsFrom( "TLeafI" ) ) {
794
795 TLeafI* tli = dynamic_cast<TLeafI*>( tobj );
796 if ( tli ) {
797 if ( tli->IsUnsigned() ) {
798 unsigned long min = 0, max = 0;
799 if ( hasRange ) {
800 min = tli->GetMinimum();
801 max = tli->GetMaximum();
802 }
803
804 item = createNTupleItem( itemName, blockName, indexName, indexRange, arraySize, min, max, ntup, hasRange );
805 } else {
806 long min = 0, max = 0;
807 if ( hasRange ) {
808 min = tli->GetMinimum();
809 max = tli->GetMaximum();
810 }
811
812 item = createNTupleItem( itemName, blockName, indexName, indexRange, arraySize, min, max, ntup, hasRange );
813 }
814 } else {
815 log << MSG::ERROR << "cannot dynamic cast to TLeafI" << endmsg;
816 }
817
818 // Float
819 } else if ( tobj->IsA()->InheritsFrom( "TLeafF" ) ) {
820 float min = 0., max = 0.;
821
822 TLeafF* tlf = dynamic_cast<TLeafF*>( tobj );
823 if ( tlf ) {
824 if ( hasRange ) {
825 min = float( tlf->GetMinimum() );
826 max = float( tlf->GetMaximum() );
827 }
828 } else {
829 log << MSG::ERROR << "cannot dynamic cast to TLeafF" << endmsg;
830 }
831
832 item = createNTupleItem( itemName, blockName, indexName, indexRange, arraySize, min, max, ntup, hasRange );
833
834 // Double
835 } else if ( tobj->IsA()->InheritsFrom( "TLeafD" ) ) {
836 double min = 0., max = 0.;
837
838 TLeafD* tld = dynamic_cast<TLeafD*>( tobj );
839 if ( tld ) {
840 if ( hasRange ) {
841 min = tld->GetMinimum();
842 max = tld->GetMaximum();
843 }
844 } else {
845 log << MSG::ERROR << "cannot dynamic cast to TLeafD" << endmsg;
846 }
847
848 item = createNTupleItem( itemName, blockName, indexName, indexRange, arraySize, min, max, ntup, hasRange );
849
850 } else {
851 log << MSG::ERROR << "Uknown data type" << endmsg;
852 }
853
854 if ( item ) {
855 ntup->add( item ).ignore();
856 } else {
857 log << MSG::ERROR << "Unable to create ntuple item \"" << itemName << "\"" << endmsg;
858 }
859
860 } // end litr
861 } // end bitr
862
863 log << MSG::DEBUG << "Total buffer size of NTuple: " << totsize << " Bytes." << endmsg;
864
865 char* buf = ntup->setBuffer( new char[totsize] );
866 char* bufpos = buf;
867
868 int ts = 0;
869 for ( const auto& iitr : itemList ) {
870 TLeaf* leaf = iitr.first;
871 int isize = iitr.second;
872
873 log << MSG::VERBOSE << "setting TBranch " << leaf->GetBranch()->GetName() << " buffer at " << (void*)bufpos
874 << endmsg;
875
876 leaf->GetBranch()->SetAddress( (void*)bufpos );
877
878 // //testing
879 // if (leaf->IsA()->InheritsFrom("TLeafI")) {
880 // for (int ievt=0; ievt<5; ievt++) {
881 // leaf->GetBranch()->GetEvent(ievt);
882 // int *idat = (int*)bufpos;
883 // log << MSG::WARNING << leaf->GetName() << ": " << ievt << " "
884 // << *idat << endmsg;
885
886 // }
887 // }
888
889 ts += isize;
890
891 bufpos += isize;
892 }
893
894 if ( totsize != ts ) { log << MSG::ERROR << "buffer size mismatch: " << ts << " " << totsize << endmsg; }
895
896 refpObject = ntup;
897
898 return StatusCode::SUCCESS;
899}
900
901// Instantiation of a static factory class used by clients to create
902// instances of this service
const char *PyHelper getProperty(IInterface *p, char *name)
#define DECLARE_CONVERTER(x)
Definition Converter.h:142
MsgStream & endmsg(MsgStream &s)
MsgStream Modifier: endmsg. Calls the output method of the MsgStream.
Definition MsgStream.h:198
SmartIF< IMessageSvc > & msgSvc() const
Retrieve pointer to message service.
SmartIF< IDataProviderSvc > & dataProvider() const override
Get Data provider service.
Definition Converter.cpp:77
A DataObject is the base class of any identifiable object on any data store.
Definition DataObject.h:37
IRegistry * registry() const
Get pointer to Registry.
Definition DataObject.h:79
std::uint64_t storedBytes() const
Bytes written to the file after the header.
Definition DiskBuffer.h:84
const std::filesystem::path & path() const
Definition DiskBuffer.h:87
void close()
Flush and close; throws on failure. Safe to call twice.
std::uint64_t bytes() const
Payload bytes of those records.
Definition DiskBuffer.h:82
std::uint64_t entries() const
Records written to the file; records lost to a failed flush do not count.
Definition DiskBuffer.h:80
Implementation of property with value of concrete type.
Definition Property.h:35
const ValueType & value() const
Definition Property.h:246
NTuple interface class definition.
Definition INTuple.h:86
std::vector< INTupleItem * > ItemContainer
Definition INTuple.h:93
virtual void reset()=0
Reset all entries to their default values.
virtual const char * buffer() const =0
Access data buffer (CONST).
virtual char * setBuffer(char *buff)=0
Attach data buffer.
virtual ItemContainer & items()=0
Access item container.
virtual StatusCode add(INTupleItem *item)=0
Add an item row to the N tuple.
virtual const std::string & title() const =0
Object title.
NTuple interface class definition.
Definition INTuple.h:32
virtual long ndim() const =0
Dimension.
virtual const void * buffer() const =0
Access data buffer (CONST).
virtual long dim(long i) const =0
Access individual dimensions.
virtual const std::string & index() const =0
Access the index _Item.
virtual long type() const =0
Type information of the item.
virtual long length() const =0
Access the buffer length.
virtual const std::string & name() const =0
Access _Item name.
virtual bool hasIndex() const =0
Is the tuple have an index item?
Opaque address interface definition.
virtual const unsigned long * ipar() const =0
Access to generic link parameters.
The IProperty is the basic interface for all components which have properties that can be set or get.
Definition IProperty.h:32
Definition of the MsgStream class used to transmit messages.
Definition MsgStream.h:29
Abstract class describing basic data in an Ntuple.
Definition NTuple.h:122
virtual const ItemRange & range() const =0
Access the range if specified.
static TYP min()
Minimal number of data.
Definition NTuple.h:84
TYP lower() const
Lower boundary of range.
Definition NTuple.h:78
TYP upper() const
Upper boundary of range.
Definition NTuple.h:80
static TYP max()
Maximal number of data.
Definition NTuple.h:86
Abstract base class which allows the user to interact with the actual N tuple implementation.
Definition NTuple.h:380
Converter of Column-wise NTuple into ROOT format.
StatusCode load(TTree *tree, INTuple *&refpObject) override
Create the transient representation of an object.
StatusCode book(const std::string &desc, INTuple *pObject, TTree *&tree) override
Book the N tuple.
StatusCode readData(TTree *rtree, INTuple *pObject, long ievt) override
Read N tuple data.
StatusCode finalize() override
std::size_t m_diskBufferBlockSize
Buffered * buffered(INTuple *nt)
StatusCode createTree(const std::string &desc, INTuple *nt, const Layout &layout, TTree *&rtree)
std::map< INTuple *, Buffered > m_buffered
StatusCode writeBuffered(Buffered &b, INTuple *nt)
std::mutex m_mutex
guards m_buffered, m_replayed and m_bufferDir
StatusCode initialize() override
StatusCode replay(IOpaqueAddress *pAddr, INTuple *nt, Buffered &b)
std::filesystem::path m_bufferDir
bool m_diskBuffer
NTupleSvc.DiskBuffer* of the service owning this stream, read once in initialize().
std::string m_diskBufferDirectory
std::set< INTuple * > m_replayed
tuples whose tree has been built; cannot be written again
StatusCode writeData(TTree *rtree, INTuple *pObject) override
Write N tuple data.
StatusCode bufferDirectory(std::filesystem::path &dir)
bool replayed(INTuple *nt)
StatusCode bookBuffered(const std::string &desc, INTuple *nt, Layout layout)
Layout analyse(const INTuple &nt)
bool m_bookDirect
force the columnar path in book()
StatusCode updateRep(IOpaqueAddress *pAddr, DataObject *pObj) override
Write the TTree; for a disk-buffered tuple, first build it from the buffer file.
std::string directory(const std::string &loc)
std::string getDirectory()
StatusCode initialize() override
Initialize the converter.
virtual std::string rootVarType(int)
Return ROOT type info:
StatusCode updateRep(IOpaqueAddress *pAddr, DataObject *pObj) override
Update the converted representation of a transient object.
SmartIF< INTupleSvc > m_ntupleSvc
Reference to N tuple service.
Definition RNTupleCnv.h:63
StatusCode finalize() override
Finalize the converter.
Small smart pointer class with automatic reference counting for IInterface.
Definition SmartIF.h:28
This class is used for returning status codes from appropriate routines.
Definition StatusCode.h:64
void ignore() const
Allow discarding a StatusCode without warning.
Definition StatusCode.h:128
constexpr static const auto SUCCESS
Definition StatusCode.h:99
constexpr static const auto FAILURE
Definition StatusCode.h:100
Append-only file of length-prefixed records, used to park ntuple rows on disk during the event loop a...
Definition DiskBuffer.h:33
@ WARNING
Definition IMessageSvc.h:22
@ DEBUG
Definition IMessageSvc.h:22
@ ERROR
Definition IMessageSvc.h:22
@ INFO
Definition IMessageSvc.h:22
@ VERBOSE
Definition IMessageSvc.h:22
bool parseName(const std::string &full, std::string &blk, std::string &var)
INTupleItem * createNTupleItem(const std::string &itemName, const std::string &blockName, const std::string &indexName, int indexRange, int arraySize, TYP min, TYP max, INTuple *ntup, bool hasRange)
Add an item of a given type to the N tuple.
std::unique_ptr< Gaudi::NTuple::DiskBuffer::Writer > writer
std::string leaflist
ROOT leaf list, e.g. "x[n][3]/F".
long unitBytes
bytes per unit of the index
std::string name
variable name (leaf name)
long indexPos
position in items() of the counting item, -1 if fixed size
How the items of a tuple map onto the staging buffer and the TTree branches.