11#define ROOTHISTCNV_RCWNTUPLECNV_CPP
37#include <fmt/format.h>
46 namespace fs = std::filesystem;
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();
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();
64 template <
typename POD>
65 decltype( auto ) downcast_item(
const INTupleItem& i ) {
68 template <
typename POD>
72 template <
typename POD,
typename T>
73 void downcast_item( T&& ) =
delete;
75 template <
typename Item,
typename F>
76 decltype( auto ) visit( Item& i, F&& f ) {
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 ) );
105 throw std::runtime_error(
"RCWNTupleCnv::visit: unknown INTupleItem::type()" );
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 )
118 long item_size =
sizeof( T );
119 long dimension = it->
length();
120 long ndim = it->
ndim() - 1;
124 std::string 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
131 }
else if ( it->
dim( ndim ) > 1 ) {
132 desc += std::to_string( it->
dim( ndim ) );
135 for (
int i = ndim - 1; i >= 0; i-- ) {
137 desc += std::to_string( it->
dim( i ) );
144 if constexpr ( std::is_integral_v<T> ) {
154 size += item_size * dimension;
160 template <
typename T>
163 std::memcpy( &v, p,
sizeof( T ) );
164 return static_cast<long>( v );
166 long indexValue(
const INTupleItem& index,
const void* p ) {
167 return visit( index, [p](
const auto& d ) {
return indexValue( d, p ); } );
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 );
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 );
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];
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 );
203 if ( left != 0 )
throw std::runtime_error(
"record longer than its layout" );
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';
220 std::mutex s_dirsMutex;
221 std::set<fs::path> s_claimedDirs;
230 const auto& items = nt.
items();
231 layout.
items.reserve( items.size() );
234 for (
const auto& i : items ) {
236 const long oldsize = size;
238 visit( *i, [&](
const auto& data ) {
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;
249 const long elemBytes = i->length() > 0 ? i->size() / i->length() : 0;
250 li.
unitBytes = elemBytes * ( i->ndim() == 2 ? i->dim( 0 ) : 1 );
257 layout.
items.push_back( std::move( li ) );
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
275 char* buff = nt->
setBuffer(
new char[size] );
277 log <<
MSG::VERBOSE <<
"Created buffer size: " << size <<
" at " << (
void*)buff <<
endmsg;
280 std::fill_n( buff, size, 0 );
286 for (
const auto& li : layout.
items ) {
288 char* buf_pos = buff + li.bufPos;
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() );
296 log <<
MSG::DEBUG <<
"adding TBranch " << br->GetTitle() <<
" at " << (
void*)buf_pos <<
endmsg;
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 );
306 index->SetMaximum( li.rangeUpper );
315 rtree->GetListOfBranches()->Add( br );
326 if ( !sc )
return sc;
338 std::initializer_list<Gaudi::Details::PropertyBase*>{ &diskBuffer, &
directory, &level, &blockSize } )
339 owner->getProperty( p ).ignore();
349 log <<
MSG::WARNING <<
"DiskBuffer: zstd not available in this build, not compressing" <<
endmsg;
352 if ( blockSize <= 0 || m_diskBufferBlockSize > DiskBuffer::maxBlockSize() ) {
353 log <<
MSG::ERROR <<
"DiskBuffer: DiskBufferBlockSize must be between 1 and " << DiskBuffer::maxBlockSize()
388 TFile* file = gDirectory ? gDirectory->GetFile() :
nullptr;
393 TUrl url( file->GetName(), kTRUE );
396 base = url.GetFile();
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;
411 if ( fs::exists( base, ec ) ) {
413 fs::remove_all( base, ec );
415 if ( !ec ) fs::create_directories( base, 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 );
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;
441 std::string
id = desc;
442 if (
auto obj =
dynamic_cast<DataObject*
>( nt ); obj && obj->
registry() )
id = obj->registry()->identifier();
444 std::lock_guard lock(
m_mutex );
447 const fs::path path = dir / fmt::format(
"{:04}.ntbuf",
m_nextBufferId++ );
449 Buffered b{ desc, std::move( layout ),
nullptr };
453 }
catch (
const DiskBuffer::Error& e ) {
457 log <<
MSG::INFO <<
"Buffering TTree with ID: " << desc <<
" \"" << nt->
title() <<
"\" in directory "
467 std::lock_guard lock(
m_mutex );
469 return it !=
m_buffered.end() ? &it->second :
nullptr;
476 std::lock_guard lock(
m_mutex );
492 <<
"\" was already written from its disk buffer: with NTupleSvc.DiskBuffer a tuple cannot be filled after "
493 "it has been saved. Row lost."
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 ); } );
516 const auto& items = nt->
items();
524 }
catch (
const DiskBuffer::Error& e ) {
546 if ( b )
return replay( pAddr, nt, *b );
558 const auto t0 = std::chrono::steady_clock::now();
559 const std::string desc = b.
desc;
561 TDirectory* pDir = (TDirectory*)pAddr->
ipar()[0];
562 TTree* tree =
nullptr;
564 std::uint64_t entries = 0, bytes = 0, stored = 0;
573 throw std::runtime_error(
"N-tuple changed shape after its first write" );
574 if ( !pDir )
throw std::runtime_error(
"no output directory" );
578 const auto& items = nt->
items();
583 for (
size_t k = 0; k < items.size(); ++k )
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() );
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" );
596 }
catch (
const std::exception& e ) {
597 log <<
MSG::ERROR <<
"Failed to build TTree " << desc <<
" from disk buffer " << path.string() <<
": " << e.what()
605 if ( sc ) fs::remove( path, ec );
607 std::lock_guard lock(
m_mutex );
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;
629 std::lock_guard lock( s_dirsMutex );
639 std::lock_guard lock(
m_mutex );
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()
664 log <<
MSG::ERROR <<
"cannot read a disk-buffered N-tuple: its TTree is only built at finalize" <<
endmsg;
667 if ( ievt >= rtree->GetEntries() ) {
669 log <<
MSG::ERROR <<
"no more entries in tree to read. max: " << rtree->GetEntries() <<
" current: " << ievt
674 rtree->GetEvent( ievt );
678 auto& items = ntup->
items();
679 std::accumulate( begin( items ), end( items ),
const_cast<const char*
>( ntup->
buffer() ),
681 return src + visit( *i, [src]( auto& item ) { return loadItem( src, item ); } );
697 std::string title = tree->GetTitle();
700 status =
m_ntupleSvc->create( CLID_ColumnWiseTuple, title, pObj );
702 if ( !ntup ) { log <<
MSG::ERROR <<
"cannot dynamic cast to INTuple" <<
endmsg; }
706 std::string itemName, indexName, item_type, itemTitle, blockName;
708 long size, totsize = 0;
709 std::vector<std::pair<TLeaf*, int>> itemList;
715 TObjArray* lbr = tree->GetListOfBranches();
717 while ( TObject* tobjb = bitr() ) {
719 TBranch* br =
dynamic_cast<TBranch*
>( tobjb );
720 itemTitle = br->GetTitle();
722 int ipos = itemTitle.find(
"::" );
724 blockName = itemTitle.substr( 0, ipos );
729 TObjArray* lf = br->GetListOfLeaves();
732 while ( TObject* tobj = litr() ) {
734 bool hasRange =
false;
739 TLeaf* tl =
dynamic_cast<TLeaf*
>( tobj );
744 itemName = tl->GetName();
746 if ( blockName !=
"" ) {
747 log <<
MSG::DEBUG <<
"loading NTuple item " << blockName <<
"/" << itemName;
749 log <<
MSG::DEBUG <<
"loading NTuple item " << itemName;
753 TLeaf* indexLeaf = tl->GetLeafCounter( arraySize );
755 if ( arraySize == 0 ) { log <<
MSG::ERROR <<
"TLeaf counter size = 0. This should not happen!" <<
endmsg; }
760 indexName = indexLeaf->GetName();
761 indexRange = indexLeaf->GetMaximum();
762 itemSize = indexRange * tl->GetLenType() * arraySize;
764 log <<
"[" << indexName;
767 if ( arraySize != 1 ) { log <<
"][" << arraySize; }
771 itemSize = tl->GetLenType() * arraySize;
775 if ( arraySize == 1 ) {
779 log <<
"[" << arraySize <<
"]";
788 hasRange = tl->IsRange();
790 itemList.emplace_back( tl, itemSize );
793 if ( tobj->IsA()->InheritsFrom(
"TLeafI" ) ) {
795 TLeafI* tli =
dynamic_cast<TLeafI*
>( tobj );
797 if ( tli->IsUnsigned() ) {
798 unsigned long min = 0, max = 0;
800 min = tli->GetMinimum();
801 max = tli->GetMaximum();
804 item =
createNTupleItem( itemName, blockName, indexName, indexRange, arraySize, min, max, ntup, hasRange );
806 long min = 0, max = 0;
808 min = tli->GetMinimum();
809 max = tli->GetMaximum();
812 item =
createNTupleItem( itemName, blockName, indexName, indexRange, arraySize, min, max, ntup, hasRange );
819 }
else if ( tobj->IsA()->InheritsFrom(
"TLeafF" ) ) {
820 float min = 0., max = 0.;
822 TLeafF* tlf =
dynamic_cast<TLeafF*
>( tobj );
825 min = float( tlf->GetMinimum() );
826 max = float( tlf->GetMaximum() );
832 item =
createNTupleItem( itemName, blockName, indexName, indexRange, arraySize, min, max, ntup, hasRange );
835 }
else if ( tobj->IsA()->InheritsFrom(
"TLeafD" ) ) {
836 double min = 0., max = 0.;
838 TLeafD* tld =
dynamic_cast<TLeafD*
>( tobj );
841 min = tld->GetMinimum();
842 max = tld->GetMaximum();
848 item =
createNTupleItem( itemName, blockName, indexName, indexRange, arraySize, min, max, ntup, hasRange );
857 log <<
MSG::ERROR <<
"Unable to create ntuple item \"" << itemName <<
"\"" <<
endmsg;
863 log <<
MSG::DEBUG <<
"Total buffer size of NTuple: " << totsize <<
" Bytes." <<
endmsg;
865 char* buf = ntup->
setBuffer(
new char[totsize] );
869 for (
const auto& iitr : itemList ) {
870 TLeaf* leaf = iitr.first;
871 int isize = iitr.second;
873 log <<
MSG::VERBOSE <<
"setting TBranch " << leaf->GetBranch()->GetName() <<
" buffer at " << (
void*)bufpos
876 leaf->GetBranch()->SetAddress( (
void*)bufpos );
894 if ( totsize != ts ) { log <<
MSG::ERROR <<
"buffer size mismatch: " << ts <<
" " << totsize <<
endmsg; }
const char *PyHelper getProperty(IInterface *p, char *name)
#define DECLARE_CONVERTER(x)
MsgStream & endmsg(MsgStream &s)
MsgStream Modifier: endmsg. Calls the output method of the MsgStream.
SmartIF< IMessageSvc > & msgSvc() const
Retrieve pointer to message service.
SmartIF< IDataProviderSvc > & dataProvider() const override
Get Data provider service.
A DataObject is the base class of any identifiable object on any data store.
IRegistry * registry() const
Get pointer to Registry.
std::uint64_t storedBytes() const
Bytes written to the file after the header.
const std::filesystem::path & path() const
void close()
Flush and close; throws on failure. Safe to call twice.
std::uint64_t bytes() const
Payload bytes of those records.
std::uint64_t entries() const
Records written to the file; records lost to a failed flush do not count.
Implementation of property with value of concrete type.
const ValueType & value() const
NTuple interface class definition.
std::vector< INTupleItem * > ItemContainer
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.
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 of the MsgStream class used to transmit messages.
Abstract class describing basic data in an Ntuple.
virtual const ItemRange & range() const =0
Access the range if specified.
static TYP min()
Minimal number of data.
TYP lower() const
Lower boundary of range.
TYP upper() const
Upper boundary of range.
static TYP max()
Maximal number of data.
Abstract base class which allows the user to interact with the actual N tuple implementation.
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.
StatusCode finalize() override
Finalize the converter.
Small smart pointer class with automatic reference counting for IInterface.
This class is used for returning status codes from appropriate routines.
void ignore() const
Allow discarding a StatusCode without warning.
constexpr static const auto SUCCESS
constexpr static const auto FAILURE
Append-only file of length-prefixed records, used to park ntuple rows on disk during the event loop a...
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)
std::string block
block name
long indexPos
position in items() of the counting item, -1 if fixed size
long maxUnits
bufLen / unitBytes
How the items of a tuple map onto the staging buffer and the TTree branches.
std::vector< Item > items