diff --git a/Database/APR/RootStorageSvc/src/RNTupleContainer.cpp b/Database/APR/RootStorageSvc/src/RNTupleContainer.cpp index 169c2770c24e48fa9b7187e0bac82530adc634a5..9242d4450175afef5b52b37e68400f35a0f8fd95 100644 --- a/Database/APR/RootStorageSvc/src/RNTupleContainer.cpp +++ b/Database/APR/RootStorageSvc/src/RNTupleContainer.cpp @@ -24,6 +24,7 @@ #include "RNTupleContainer.h" #include "RootDataPtr.h" #include "RootDatabase.h" +#include "RNTupleWriterHelper.h" // Root include files #include "ROOT/RNTuple.hxx" @@ -184,6 +185,14 @@ DbStatus RNTupleContainer::open( DbDatabase& dbH, const std::string& nam, log << DbPrintLvl::Debug << "Adding new RNTuple Field: name=" << dsc.fieldname << " typename=" << dsc.typeName() << DbPrint::endmsg; m_ntupleWriter->addField( dsc.fieldname, dsc.typeName() ); + if( dsc.hasAuxStore() ) { + dsc.auxdyn_writer = RootAuxDynIO::getNTupleAuxDynWriter(); + if( !dsc.auxdyn_writer ) { + log << DbPrintLvl::Error << "Cannot get AuxDyn writer for " << dsc.fieldname + << DbPrint::endmsg; + return Error; + } + } } } else if( mode & (pool::READ | pool::UPDATE) ) { @@ -310,8 +319,11 @@ DbStatus RNTupleContainer::writeObject( ActionList::value_type& action ) case DbColumn::POINTER: dsc.object = p.ptr; try { - if( dsc.hasAuxStore() ) { - num_bytes += m_ntupleWriter->writeAuxAttributes( dsc.fieldname, dsc.getIOStorePtr(), dsc.rows_written ); + if( dsc.auxdyn_writer ) { + auto attrList = dsc.auxdyn_writer->collectAuxAttributes( dsc.fieldname, dsc.getIOStorePtr() ); + for(const auto& itr : attrList) { + m_ntupleWriter->addAttribute( itr ); + } } } catch(const std::exception& exc) { DbPrint err(m_name); diff --git a/Database/APR/RootStorageSvc/src/RNTupleContainer.h b/Database/APR/RootStorageSvc/src/RNTupleContainer.h index 519d47d31b03f08f708f334e5ff61f0cfbc35db0..ea136a0d607a8d6da112e61c1aa3415e4532b24e 100644 --- a/Database/APR/RootStorageSvc/src/RNTupleContainer.h +++ b/Database/APR/RootStorageSvc/src/RNTupleContainer.h @@ -22,7 +22,8 @@ // Forward declarations class TClass; namespace SG { class IAuxStoreIO; } -namespace RootAuxDynIO { class IRNTupleWriter; } +namespace RootAuxDynIO { class IRootAuxDynReader; class IRNTupleAuxDynWriter; } +namespace RootStorageSvc { class RNTupleWriterHelper; } namespace ROOT::Experimental { class RNTupleReader; } #include "ROOT/RNTupleView.hxx" @@ -70,6 +71,9 @@ class RNTupleContainer : public DbContainerImp // AuxDyn RNTuple reader (managed by the Database) std::unique_ptr<RootAuxDynIO::IRootAuxDynReader> auxdyn_reader; + // AuxDyn RNTuple writer (managed by the Database) + std::unique_ptr<RootAuxDynIO::IRNTupleAuxDynWriter> auxdyn_writer; + FieldDesc(const DbColumn& c); FieldDesc(FieldDesc const& other) = delete; FieldDesc(FieldDesc&& other) = default; @@ -107,8 +111,10 @@ class RNTupleContainer : public DbContainerImp int64_t m_indexBump; const uint32_t m_indexMulti; - RootAuxDynIO::IRNTupleWriter* m_ntupleWriter = nullptr; - /// Note: the Fields need to be destroyed before the page source is gone + /// Internal cache of the RNTupleWriterHelper + RootStorageSvc::RNTupleWriterHelper* m_ntupleWriter = nullptr; + + /// Internal cache of the native RNTupleReader RNTupleReader* m_ntupleReader{}; public: diff --git a/Database/APR/RootStorageSvc/src/RNTupleWriterHelper.cpp b/Database/APR/RootStorageSvc/src/RNTupleWriterHelper.cpp new file mode 100644 index 0000000000000000000000000000000000000000..2842b1d32610d57566995a97cb22319ca2805240 --- /dev/null +++ b/Database/APR/RootStorageSvc/src/RNTupleWriterHelper.cpp @@ -0,0 +1,167 @@ +/* + Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration +*/ + +#include "RNTupleWriterHelper.h" + +#include "ROOT/RNTupleModel.hxx" +#include "RootUtils/APRDefaults.h" +#include "TFile.h" + +namespace RootStorageSvc { + +RNTupleWriterHelper::RNTupleWriterHelper(TFile* file, + const std::string& ntupleName, + bool enableBufferedWrite, + bool enableMetrics) + : AthMessaging(std::string("RNTupleWriterHelper[") + ntupleName + "]"), + m_model(RNTupleModel::Create()), + m_ntupleName(ntupleName), + m_tfile(file), + m_collectMetrics(enableMetrics) { + m_opts.SetCompression(m_tfile->GetCompressionSettings()); + m_opts.SetUseBufferedWrite(enableBufferedWrite); + m_model->SetDescription(ntupleName); + RNTupleWriterHelper::addField(APRDefaults::IndexColName, "std::uint64_t"); +} + +void RNTupleWriterHelper::makeNewEntry() { + if (m_model) { + // prepare for writing of the first row + if (!m_tfile) { + throw std::runtime_error(std::string("Attempt to write RNTuple ") + + m_ntupleName + " without valid TFile ptr"); + } else { + // write into existing file + ATH_MSG_DEBUG("Creating RNTuple " << m_tfile->GetName() << "/" + << m_ntupleName); + m_ntupleWriter = RNTupleWriter::Append(std::move(m_model), m_ntupleName, + *m_tfile, m_opts); + if (m_collectMetrics) + m_ntupleWriter->EnableMetrics(); + } + } +#if ROOT_VERSION_CODE >= ROOT_VERSION(6, 31, 0) + m_entry = m_ntupleWriter->GetModel().CreateBareEntry(); +#else + m_entry = m_ntupleWriter->GetModel()->CreateBareEntry(); +#endif +} + +void RNTupleWriterHelper::addAttribute(const attrDataTuple& in) { + const std::string& name = std::get<0>(in); + const std::string& type = std::get<1>(in); + void* data = std::get<2>(in); + ATH_MSG_DEBUG("Adding Dynamic Attribute " << name << " of type " << type); + if (m_attrDataMap.find(name) == m_attrDataMap.end()) { + addField(name, type); + } + addFieldValue(name, data); +} + +/// Add a new field to the RNTuple +/// Used for data objects from RNTupleContainer, not for dynamic attributes +void RNTupleWriterHelper::addField(const std::string& field_name, + const std::string& attr_type) { + if (m_attrDataMap.find(field_name) != m_attrDataMap.end()) { + throw std::runtime_error( + std::string("Attempt to add existing field. name: ") + field_name + + "new type: " + attr_type); + } + ATH_MSG_DEBUG("Adding new object column, name=" << field_name << " of type " + << attr_type); + auto field = RFieldBase::Create(field_name, attr_type).Unwrap(); + if (!m_model) { +#if ROOT_VERSION_CODE >= ROOT_VERSION(6, 31, 0) + // first write was already done, need to update the model + ATH_MSG_DEBUG("Adding late attribute " << field_name); + auto updater = m_ntupleWriter->CreateModelUpdater(); + updater->BeginUpdate(); + updater->AddField(std::move(field)); + updater->CommitUpdate(); +#endif + } else { + m_model->AddField(std::move(field)); + } + m_attrDataMap[field_name] = nullptr; +} + +/// Supply data address for a given field +void RNTupleWriterHelper::addFieldValue(const std::string& field_name, + void* attr_data) { + auto field_iter = m_attrDataMap.find(field_name); + if (field_iter == m_attrDataMap.end()) { + std::stringstream msg; + msg << "Attempt to write unknown Field with name: '" << field_name + << std::ends; + throw std::runtime_error(msg.str()); + } + // already started writing + field_iter->second = attr_data; + m_needsCommit = true; +} + +int RNTupleWriterHelper::commit() { +#if ROOT_VERSION_CODE >= ROOT_VERSION(6, 31, 0) + // write only if there was data added, ignore empty commits + if (!needsCommit()) { + ATH_MSG_DEBUG("Empty Commit"); + return 0; + } + ATH_MSG_DEBUG("Commit, row=" << m_rowN << " : " << m_ntupleName); + if (!m_entry) + makeNewEntry(); + // update index field before commit + + int num_bytes = 0; + ATH_MSG_DEBUG(m_ntupleName << " has " << m_attrDataMap.size() + << " attributes"); + int attrN = 0; + for (auto& attr : m_attrDataMap) { + ATH_MSG_VERBOSE("Setting data ptr for field# " + << ++attrN << ": " << attr.first << " data=" << std::hex + << attr.second << std::dec); + // If an object already exists bind it to the field + // Otherwise, create a new value and use its address + if (attr.second) { + m_entry->BindRawPtr(attr.first, attr.second); + } else { + m_entry->EmplaceNewValue(attr.first); + attr.second = m_entry->GetPtr<void>(attr.first).get(); + } + } + num_bytes += m_ntupleWriter->Fill(*m_entry); + ATH_MSG_DEBUG("Filled RNTuple Row, bytes written: " << num_bytes); + + m_entry.reset(); + // forget all values to see if any object is missing in a new commit + for (auto& attr : m_attrDataMap) { + attr.second = nullptr; + } + m_needsCommit = false; + m_rowN++; + + return num_bytes; +#else + ATH_MSG_WARNING("Commit not implemented for this ROOT version"); + return 0; +#endif +} + +void RNTupleWriterHelper::close() { + // Print metrics if enabled - this can become DEBUG/VERBOSE + if (m_ntupleWriter->GetMetrics().IsEnabled()) { + auto& log = msg(MSG::INFO); + log << "Printing I/O Statistics\n"; + m_ntupleWriter->GetMetrics().Print(log.stream()); + log << endmsg; + } + // delete the generated default fields (RField should delete the default data + // objects) + m_ntupleWriter.reset(); + m_entry.reset(); + m_model.reset(); + m_rowN = 0; +} + +} // namespace RootStorageSvc diff --git a/Database/APR/RootStorageSvc/src/RNTupleWriterHelper.h b/Database/APR/RootStorageSvc/src/RNTupleWriterHelper.h new file mode 100644 index 0000000000000000000000000000000000000000..84a8644fd377a5aab8f436e8581cd9c10e98572d --- /dev/null +++ b/Database/APR/RootStorageSvc/src/RNTupleWriterHelper.h @@ -0,0 +1,111 @@ +/* + Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration +*/ + +#ifndef RNTUPLEWRITERHELPER_H +#define RNTUPLEWRITERHELPER_H + +#include "AthenaBaseComps/AthMessaging.h" +#include "ROOT/REntry.hxx" +#include "ROOT/RField.hxx" +#include "ROOT/RNTuple.hxx" +#if ROOT_VERSION_CODE >= ROOT_VERSION(6, 31, 0) +#include "ROOT/RNTupleWriteOptions.hxx" +#include "ROOT/RNTupleWriter.hxx" +#else +#include "ROOT/RNTupleOptions.hxx" +#endif + +#include <tuple> + +namespace ROOT::Experimental { +class RNTupleModel; +} + +namespace RootStorageSvc { +#if ROOT_VERSION_CODE >= ROOT_VERSION(6, 31, 0) +using RFieldBase = ROOT::Experimental::RFieldBase; +#else +using RFieldBase = ROOT::Experimental::Detail::RFieldBase; +#endif +using RNTupleWriter = ROOT::Experimental::RNTupleWriter; +using RNTupleModel = ROOT::Experimental::RNTupleModel; +using REntry = ROOT::Experimental::REntry; +using RNTupleWriteOptions = ROOT::Experimental::RNTupleWriteOptions; + +class RNTupleWriterHelper : public AthMessaging { + public: + /// Constructor + RNTupleWriterHelper(TFile* file, const std::string& ntupleName, + bool enableBufferedWrite, bool enableMetrics); + + /// Default Destructor + ~RNTupleWriterHelper() = default; + + /// Create a new empty RNTuple row with the current model (fields) + void makeNewEntry(); + + /// Add a new field to the RNTuple, collect the data pointer for the commit + // The convention for the tuple is <name, type, data> + typedef std::tuple<std::string, std::string, void*> attrDataTuple; + void addAttribute(const attrDataTuple& in); + + /// Add a new field to the RNTuple + void addField(const std::string& field_name, const std::string& attr_type); + + /// Supply data address for a given field + void addFieldValue(const std::string& field_name, void* attr_data); + + /// Commit the data + int commit(); + + /// Name of the RNTuple + const std::string& getName() const { return m_ntupleName; } + + /// Size of the RNTuple + size_t size() const { return m_rowN; } + + /// Check if any data needs to be committed + bool needsCommit() const { return m_needsCommit; } + + /// Is this RNTuple used by more than one APR container? + bool isGrouped() const { return m_clients > 1; } + + /// Keep track of how many APR containers are writing to this RNTuple + void increaseClientCount() { m_clients++; } + + /// Close the writer + void close(); + + private: + /// Store data ptr for the first row, when only creating the model + std::map<std::string, void*> m_attrDataMap; + + /// Internal cache for the RNTuple model + /// Before first commit the fields are added to the model + /// At the first commit the model passed to the RNTupleWriter + /// which takes its ownership + std::unique_ptr<RNTupleModel> m_model; + + /// Internal cache for the RNEntry + std::unique_ptr<REntry> m_entry; + + /// Internal cache for the native RNTupleWriter + std::unique_ptr<RNTupleWriter> m_ntupleWriter; + + std::string m_ntupleName; + TFile* m_tfile; + RNTupleWriteOptions m_opts; + int m_rowN = 0; + + /// Count how many APR Containers are writing to this RNTuple (more than one + /// makes a Group) + int m_clients = 0; + bool m_needsCommit = false; + + /// Enable/Disable Metric Collection + bool m_collectMetrics; +}; + +} // namespace RootStorageSvc +#endif diff --git a/Database/APR/RootStorageSvc/src/RootDatabase.cpp b/Database/APR/RootStorageSvc/src/RootDatabase.cpp index 13aba110ed3fd4202069fe3543ec9f6d634694fb..03f24ba80d8505f6d72ee5dd67d2e17cf171441f 100644 --- a/Database/APR/RootStorageSvc/src/RootDatabase.cpp +++ b/Database/APR/RootStorageSvc/src/RootDatabase.cpp @@ -18,6 +18,8 @@ #include "POOLCore/DbPrint.h" #include "RootUtils/APRDefaults.h" #include "RootAuxDynIO/RootAuxDynIO.h" +#include "RNTupleWriterHelper.h" +#include "RootUtils/APRDefaults.h" #include "GaudiKernel/Bootstrap.h" #include "GaudiKernel/ConcurrencyFlags.h" @@ -920,16 +922,16 @@ DbStatus RootDatabase::transAct(Transaction::Action action) if (cl != nullptr && cl->InheritsFrom("TTree")) { TTree* tree = static_cast<TTree*>(m_file->Get(key->GetName())); DbPrint log( m_file->GetName() ); - if (tree != nullptr && tree->GetBranch("index_ref") != nullptr && tree->GetEntries() > 0) { + if (tree != nullptr && tree->GetBranch(APRDefaults::IndexColName) != nullptr && tree->GetEntries() > 0) { TList* friendTrees(tree->GetListOfFriends()); if (friendTrees != nullptr && !friendTrees->IsEmpty()) { - log << DbPrintLvl::Debug << "BuildIndex for index_ref to " << tree->GetName() << DbPrint::endmsg; - tree->BuildIndex("index_ref"); + log << DbPrintLvl::Debug << "BuildIndex for " << APRDefaults::IndexColName << " to " << tree->GetName() << DbPrint::endmsg; + tree->BuildIndex(APRDefaults::IndexColName); for (const auto&& obj: *friendTrees) { TTree* friendTree = tree->GetFriend(obj->GetName()); - if (friendTree != nullptr && friendTree->GetBranch("index_ref") != nullptr && friendTree->GetEntries() > 0) { - log << DbPrintLvl::Debug << "BuildIndex for index_ref to " << friendTree->GetName() << DbPrint::endmsg; - friendTree->BuildIndex("index_ref"); + if (friendTree != nullptr && friendTree->GetBranch(APRDefaults::IndexColName) != nullptr && friendTree->GetEntries() > 0) { + log << DbPrintLvl::Debug << "BuildIndex for " << APRDefaults::IndexColName << " to " << friendTree->GetName() << DbPrint::endmsg; + friendTree->BuildIndex(APRDefaults::IndexColName); } } } @@ -1118,7 +1120,7 @@ RootDatabase::getNTupleReader(const std::string& ntuple_name) } -RootAuxDynIO::IRNTupleWriter* +RootStorageSvc::RNTupleWriterHelper* RootDatabase::getNTupleWriter(const std::string& ntuple_name, bool create) { auto& writer = m_ntupleWriterMap[ntuple_name]; @@ -1127,7 +1129,7 @@ RootDatabase::getNTupleWriter(const std::string& ntuple_name, bool create) DbPrint log("RootDatabase.getNTupleWriter"); log << DbPrintLvl::Warning << "Buffered writing doesn't work reliably in MT jobs yet, use at your own risk!" << DbPrint::endmsg; } - writer = RootAuxDynIO::getNTupleAuxDynWriter(m_file, ntuple_name, m_rntBufferedWriteEnabled, m_rntWriterMetricsEnabled); + writer = std::make_unique<RootStorageSvc::RNTupleWriterHelper>(m_file, ntuple_name, m_rntBufferedWriteEnabled, m_rntWriterMetricsEnabled); } if( writer and create ) { // treat the create flag as an indication of a new container client and count them @@ -1147,7 +1149,7 @@ uint64_t RootDatabase::indexLookup([[maybe_unused]] RNTupleReader* reader, uint6 indexLookup_t &index = m_ntupleIndexMap[reader]; index.reserve(reader->GetNEntries()); // This is the field in which the indices are kept - auto indexRefField = reader->GetView<uint64_t>("index_ref"); + auto indexRefField = reader->GetView<uint64_t>(APRDefaults::IndexColName); // Loop over the events, read the index values and fill the map uint64_t row{0}; for(const auto& entry : reader->GetEntryRange()) { diff --git a/Database/APR/RootStorageSvc/src/RootDatabase.h b/Database/APR/RootStorageSvc/src/RootDatabase.h index a90f2eadecf2f4aa7b9f3b80de8827c83ee0fd13..e21683e21728f2041f8e4a07512ec8e29f44924a 100644 --- a/Database/APR/RootStorageSvc/src/RootDatabase.h +++ b/Database/APR/RootStorageSvc/src/RootDatabase.h @@ -20,7 +20,6 @@ #include <mutex> #include <unordered_map> - // Forward declarations namespace ROOT { namespace Experimental { class RNTupleReader; @@ -33,7 +32,9 @@ class TBranch; class IFileMgr; namespace RootAuxDynIO { class IRootAuxDynReader; - class IRNTupleWriter; +} +namespace RootStorageSvc { + class RNTupleWriterHelper; } /* @@ -124,7 +125,7 @@ namespace pool { // mutex to prevent concurrent read I/O from AuxDynReader std::recursive_mutex m_iomutex; - std::map<std::string, std::unique_ptr<RootAuxDynIO::IRNTupleWriter> > m_ntupleWriterMap; + std::map<std::string, std::unique_ptr<RootStorageSvc::RNTupleWriterHelper> > m_ntupleWriterMap; std::map<std::string, std::unique_ptr<RNTupleReader> > m_ntupleReaderMap; using indexLookup_t = std::unordered_map<uint64_t, uint64_t>; @@ -244,9 +245,9 @@ namespace pool { // translate index value to row# for a given RNTuple uint64_t indexLookup(RNTupleReader *ps, uint64_t idx_val); - /// return NTupleWriter for a given ntuple_name + /// Return RNTupleWriterHelper for a given ntuple_name /// create a new one if needed when create==true - RootAuxDynIO::IRNTupleWriter* getNTupleWriter(const std::string& ntuple_name, bool create=false); + RootStorageSvc::RNTupleWriterHelper* getNTupleWriter(const std::string& ntuple_name, bool create=false); protected: // Execute any pending Fills before commit or flush diff --git a/Database/AthenaRoot/RootAuxDynIO/RootAuxDynIO/RootAuxDynIO.h b/Database/AthenaRoot/RootAuxDynIO/RootAuxDynIO/RootAuxDynIO.h index c2bbbabd29e3490451b6c5bc42229a33325ad7d6..9f646f863fcf854a50c20304a1dfbc5e1b63c16a 100644 --- a/Database/AthenaRoot/RootAuxDynIO/RootAuxDynIO/RootAuxDynIO.h +++ b/Database/AthenaRoot/RootAuxDynIO/RootAuxDynIO/RootAuxDynIO.h @@ -9,7 +9,9 @@ #include <string> #include <memory> +#include <vector> #include <mutex> +#include <tuple> #include "RootAuxDynIO/RootAuxDynDefs.h" class TBranch; @@ -17,7 +19,7 @@ class TTree; class TFile; class TClass; -namespace ROOT { namespace Experimental { +namespace ROOT::Experimental { class RNTupleReader; #if ROOT_VERSION_CODE < ROOT_VERSION( 6, 31, 0 ) namespace Detail { @@ -26,7 +28,7 @@ namespace ROOT { namespace Experimental { #else class RFieldBase; #endif -} } +} namespace SG { class IAuxStoreIO; class auxid_set_t; } @@ -40,6 +42,7 @@ namespace RootAuxDynIO using ROOT::Experimental::RNTupleReader; class IRootAuxDynReader; class IRootAuxDynWriter; + class IRNTupleAuxDynWriter; class IRNTupleWriter; /// check if a field/branch with fieldname and type tc has IAuxStore interface @@ -61,9 +64,12 @@ namespace RootAuxDynIO std::unique_ptr<IRootAuxDynWriter> getBranchAuxDynWriter(TTree*, int bufferSize, int splitLevel, int offsettab_len, bool do_branch_fill); - std::unique_ptr<IRootAuxDynReader> getNTupleAuxDynReader(const std::string& field_name, const std::string& field_type, RNTupleReader* reader); - std::unique_ptr<IRNTupleWriter> getNTupleAuxDynWriter(TFile*, const std::string& ntupleName, bool enableBufferedWrite, bool enableMetrics); + std::unique_ptr<IRootAuxDynReader> getNTupleAuxDynReader(const std::string& field_name, const std::string& field_type, RNTupleReader* reader); + std::unique_ptr<IRNTupleAuxDynWriter> getNTupleAuxDynWriter(); + std::unique_ptr<IRNTupleWriter> getNTupleWriter(TFile*, const std::string& ntupleName, bool enableBufferedWrite, bool enableMetrics); + // The convention for the tuple is <name, type, data> + typedef std::tuple<std::string, std::string, void*> attrDataTuple; class IRootAuxDynReader { @@ -108,39 +114,15 @@ namespace RootAuxDynIO virtual void setBranchFillMode(bool) = 0; }; - - /// Interface for a generic RNTuple-based Writer (can handle both normal objects and AuxDyn attributes - class IRNTupleWriter { + /// Interface for a RNTuple-based Writer that handles AuxDyn attributes + /// Works in conjuction with the generic writer + class IRNTupleAuxDynWriter { public: - virtual ~IRNTupleWriter() {} - - virtual const std::string& getName() const = 0; - - virtual size_t size() const = 0; - - /// Add a new field to the RNTuple - virtual void addField( const std::string& field_name, const std::string& attr_type ) = 0; - - /// Supply data address for a given field - virtual void addFieldValue( const std::string& field_name, void* attr_data ) = 0; - - /// handle writing of dynamic xAOD attributes of an AuxContainer - called from RNTupleContainer::writeObject() - /// should report bytes written - it does not do than yet though - // may throw exceptions - virtual int writeAuxAttributes(const std::string& base_branch, SG::IAuxStoreIO* store, size_t rows_written ) = 0; - - /// Add a APR container to this RNTuple - if there is more than one than do grouped DB commit - virtual void increaseClientCount() = 0; - /// Check if there is more than one container writing to this RNTuple - virtual bool isGrouped() const = 0; - - /// is there a need to call commit()? - virtual bool needsCommit() const = 0; - - /// Call Fill() on the ROOT object used by this writer - virtual int commit() = 0; + /// Default Destructor + virtual ~IRNTupleAuxDynWriter() = default; - virtual void close() = 0; + /// Collect Aux data information to be writting out + virtual std::vector<attrDataTuple> collectAuxAttributes( const std::string& base_branch, SG::IAuxStoreIO* store ) = 0; }; } // namespace diff --git a/Database/AthenaRoot/RootAuxDynIO/src/RNTupleAuxDynReader.h b/Database/AthenaRoot/RootAuxDynIO/src/RNTupleAuxDynReader.h index 2afb0eabad618ce26dff680c9d8bcfdbb579580c..2874b3ac071d52f02891ff5e1d0cca2c06d330a9 100644 --- a/Database/AthenaRoot/RootAuxDynIO/src/RNTupleAuxDynReader.h +++ b/Database/AthenaRoot/RootAuxDynIO/src/RNTupleAuxDynReader.h @@ -14,7 +14,6 @@ #include <map> #include <string> -namespace RootAuxDynIO { class IRNTupleWriter; } namespace ROOT::Experimental { class RNTupleReader; } diff --git a/Database/AthenaRoot/RootAuxDynIO/src/RNTupleAuxDynWriter.cxx b/Database/AthenaRoot/RootAuxDynIO/src/RNTupleAuxDynWriter.cxx index d0e37613be875c5b6ff276483e3c4903281d285c..adc0a40698a0fe134e7755ce837a409905f905c8 100644 --- a/Database/AthenaRoot/RootAuxDynIO/src/RNTupleAuxDynWriter.cxx +++ b/Database/AthenaRoot/RootAuxDynIO/src/RNTupleAuxDynWriter.cxx @@ -3,184 +3,34 @@ */ #include "RNTupleAuxDynWriter.h" + #include "AthContainers/AuxStoreInternal.h" #include "AthContainers/AuxTypeRegistry.h" #include "AthContainers/normalizedTypeinfoName.h" -#include "TFile.h" -#include "Compression.h" -#include "TClass.h" -#include "TVirtualCollectionProxy.h" - -#include "ROOT/RNTupleModel.hxx" - -#include <sstream> -#include <iostream> - - -namespace RootAuxDynIO -{ - - RNTupleAuxDynWriter::RNTupleAuxDynWriter(TFile* file, const std::string& ntupleName, bool enableBufferedWrite, bool enableMetrics) : - AthMessaging(std::string("RNTupleAuxDynWriter[")+ntupleName+"]"), - m_model( RNTupleModel::Create() ), - m_ntupleName( ntupleName ), - m_tfile( file ), - m_collectMetrics( enableMetrics ) - { - m_opts.SetCompression( m_tfile->GetCompressionSettings() ); - m_opts.SetUseBufferedWrite( enableBufferedWrite ); - m_model->SetDescription( ntupleName ); - RNTupleAuxDynWriter::addField("index_ref", "std::uint64_t"); - } - - - void RNTupleAuxDynWriter::makeNewEntry() { - if( m_model ) { - // prepare for writing of the first row - if( !m_tfile ) { - throw std::runtime_error( std::string("Attempt to write RNTuple ") + m_ntupleName + " without valid TFile ptr" ); - } else { - // write into existing file - ATH_MSG_DEBUG("Creating RNTuple " << m_tfile->GetName() << "/" << m_ntupleName); - m_ntupleWriter = RNTupleWriter::Append(std::move(m_model), m_ntupleName, *m_tfile, m_opts); - if( m_collectMetrics ) m_ntupleWriter->EnableMetrics(); - } - } -#if ROOT_VERSION_CODE >= ROOT_VERSION( 6, 31, 0 ) - m_entry = m_ntupleWriter->GetModel().CreateBareEntry(); -#else - m_entry = m_ntupleWriter->GetModel()->CreateBareEntry(); -#endif - } - - - /// handle writing of dynamic xAOD attributes of an object - called from RootTreeContainer::writeObject() - // throws exceptions - int RNTupleAuxDynWriter::writeAuxAttributes( const std::string& base_name, SG::IAuxStoreIO* store, size_t /*rows_written*/ ) - { - const SG::auxid_set_t selection = store->getSelectedAuxIDs(); - ATH_MSG_DEBUG("Writing " << base_name << " with " << selection.size() << " Dynamic attributes"); - for(SG::auxid_t id : selection) { - const std::string attr_type = SG::normalizedTypeinfoName( *store->getIOType(id) ); - const std::string attr_name = SG::AuxTypeRegistry::instance().getName(id); - const std::string field_name = RootAuxDynIO::auxFieldName( attr_name, base_name ); - void* attr_data ATLAS_THREAD_SAFE = const_cast<void*>( store->getIOData(id) ); - - addAttribute( field_name, attr_type, attr_data ); - } - return 0; // MN: can get bytes written only when calling Fill() at commit - } - - - void RNTupleAuxDynWriter::addAttribute( const std::string& field_name, const std::string& attr_type, void* attr_data ) - { - if( m_attrDataMap.find(field_name) == m_attrDataMap.end() ) { - addField(field_name, attr_type); - } - addFieldValue(field_name, attr_data); - } - - /// Add a new field to the RNTuple - /// Used for data objects from RNTupleContainer, not for dynamic attributes - void RNTupleAuxDynWriter::addField( const std::string& field_name, const std::string& attr_type ) - { - if( m_attrDataMap.find(field_name) != m_attrDataMap.end() ) { - throw std::runtime_error( std::string("Attempt to add existing field. name: ") - + field_name + "new type: " + attr_type ); - } - ATH_MSG_DEBUG("Adding new object column, name="<< field_name << " of type " << attr_type); - auto field = RFieldBase::Create(field_name, attr_type).Unwrap(); - if( !m_model ) { -#if ROOT_VERSION_CODE >= ROOT_VERSION( 6, 31, 0 ) - // first write was already done, need to update the model - ATH_MSG_DEBUG("Adding late attribute " << field_name); - auto updater = m_ntupleWriter->CreateModelUpdater(); - updater->BeginUpdate(); - updater->AddField( std::move(field) ); - updater->CommitUpdate(); -#endif - } else { - m_model->AddField( std::move(field) ); - } - m_attrDataMap[ field_name ] = nullptr; - } - - - /// Supply data address for a given field - void RNTupleAuxDynWriter::addFieldValue( const std::string& field_name, void* attr_data ) - { - auto field_iter = m_attrDataMap.find(field_name); - if( field_iter == m_attrDataMap.end() ) { - std::stringstream msg; - msg <<"Attempt to write unknown Field with name: '" << field_name << std::ends; - throw std::runtime_error( msg.str() ); - } - // already started writing - field_iter->second = attr_data; - m_needsCommit = true; - } - - - int RNTupleAuxDynWriter::commit() -{ -#if ROOT_VERSION_CODE >= ROOT_VERSION( 6, 31, 0 ) - // write only if there was data added, ignore empty commits - if( !needsCommit() ) { - ATH_MSG_DEBUG("Empty Commit"); - return 0; - } - ATH_MSG_DEBUG("Commit, row=" << m_rowN << " : " << m_ntupleName ); - if( !m_entry ) makeNewEntry(); - // update index field before commit - - int num_bytes = 0; - ATH_MSG_DEBUG(m_ntupleName << " has " << m_attrDataMap.size() << " attributes"); - int attrN = 0; - for( auto& attr: m_attrDataMap ) { - ATH_MSG_VERBOSE("Setting data ptr for field# " << ++attrN << ": " << attr.first << " data=" << std::hex << attr.second << std::dec ); - // If an object already exists bind it to the field - // Otherwise, create a new value and use its address - if( attr.second ) { - m_entry->BindRawPtr( attr.first, attr.second ); - } else { - m_entry->EmplaceNewValue( attr.first ); - attr.second = m_entry->GetPtr<void>( attr.first ).get(); - } - } - num_bytes += m_ntupleWriter->Fill( *m_entry ); - ATH_MSG_DEBUG("Filled RNTuple Row, bytes written: " << num_bytes); - - m_entry.reset(); - // forget all values to see if any object is missing in a new commit - for( auto& attr: m_attrDataMap ) { attr.second = nullptr; } - m_needsCommit = false; - m_rowN++; - - return num_bytes; -#else - ATH_MSG_WARNING("Commit not implemented for this ROOT version"); - return 0; -#endif - } - - - void RNTupleAuxDynWriter::close() { - // Print metrics if enabled - this can become DEBUG/VERBOSE - if( m_ntupleWriter->GetMetrics().IsEnabled() ) { - auto& log = msg(MSG::INFO); - log << "Printing I/O Statistics\n"; - m_ntupleWriter->GetMetrics().Print(log.stream()); - log << endmsg; - } - // delete the generated default fields (RField should delete the default data objest) - m_ntupleWriter.reset(); m_entry.reset(); m_model.reset(); m_rowN=0; - } - - - RNTupleAuxDynWriter::~RNTupleAuxDynWriter() {} - -}// namespace - - - +namespace RootAuxDynIO { +/// Simple Constructor +RNTupleAuxDynWriter::RNTupleAuxDynWriter() + : AthMessaging(std::string("RNTupleAuxDynWriter")) {} + +/// Collect Aux data information to be written out +std::vector<attrDataTuple> RNTupleAuxDynWriter::collectAuxAttributes( + const std::string& base_name, SG::IAuxStoreIO* store) { + std::vector<attrDataTuple> result; + const SG::auxid_set_t selection = store->getSelectedAuxIDs(); + ATH_MSG_DEBUG("Writing " << base_name << " with " << selection.size() + << " Dynamic attributes"); + for (SG::auxid_t id : selection) { + const std::string attr_type = + SG::normalizedTypeinfoName(*store->getIOType(id)); + const std::string attr_name = SG::AuxTypeRegistry::instance().getName(id); + const std::string field_name = + RootAuxDynIO::auxFieldName(attr_name, base_name); + void* attr_data ATLAS_THREAD_SAFE = const_cast<void*>(store->getIOData(id)); + + result.emplace_back(field_name, attr_type, attr_data); + } + return result; +} + +} // namespace RootAuxDynIO diff --git a/Database/AthenaRoot/RootAuxDynIO/src/RNTupleAuxDynWriter.h b/Database/AthenaRoot/RootAuxDynIO/src/RNTupleAuxDynWriter.h index d7fa4f30e5d58bf231fc2577269b9fd1267cbb09..1c4d7e8bef18419caef1b6146d9254ba233a8302 100644 --- a/Database/AthenaRoot/RootAuxDynIO/src/RNTupleAuxDynWriter.h +++ b/Database/AthenaRoot/RootAuxDynIO/src/RNTupleAuxDynWriter.h @@ -8,97 +8,24 @@ #include "AthenaBaseComps/AthMessaging.h" #include "RootAuxDynIO/RootAuxDynIO.h" -#include "ROOT/RNTuple.hxx" -#include "ROOT/RField.hxx" -#include "ROOT/REntry.hxx" -#if ROOT_VERSION_CODE >= ROOT_VERSION( 6, 31, 0 ) -#include "ROOT/RNTupleWriter.hxx" -#include "ROOT/RNTupleWriteOptions.hxx" -#else -#include "ROOT/RNTupleOptions.hxx" -#endif - -namespace ROOT { namespace Experimental { - class RNTupleModel; -} } - -namespace SG { class IAuxStoreIO; } - - -namespace RootAuxDynIO -{ -#if ROOT_VERSION_CODE >= ROOT_VERSION( 6, 31, 0 ) - using RFieldBase = ROOT::Experimental::RFieldBase; -#else - using RFieldBase = ROOT::Experimental::Detail::RFieldBase; -#endif - using RNTupleWriter = ROOT::Experimental::RNTupleWriter; - using RNTupleModel = ROOT::Experimental::RNTupleModel; - using REntry = ROOT::Experimental::REntry; - using RNTupleWriteOptions = ROOT::Experimental::RNTupleWriteOptions; - - - class RNTupleAuxDynWriter : public AthMessaging, public IRNTupleWriter - { - public: - - // store data ptr for the first row, when only creating the model - std::map<std::string, void*> m_attrDataMap; - - std::unique_ptr<RNTupleModel> m_model; - std::unique_ptr<REntry> m_entry; - std::unique_ptr<RNTupleWriter> m_ntupleWriter; - - std::string m_ntupleName; - TFile* m_tfile; - RNTupleWriteOptions m_opts; - int m_rowN = 0; - - /// Count how many APR Containers are writing to this RNTuple (more than one makes a Group) - int m_clients = 0; - bool m_needsCommit = false; +namespace SG { +class IAuxStoreIO; +} - /// Enable/Disable Metric Collection - bool m_collectMetrics; +namespace RootAuxDynIO { - RNTupleAuxDynWriter(TFile* file, const std::string& ntupleName, bool enableBufferedWrite, bool enableMetrics); +class RNTupleAuxDynWriter : public AthMessaging, public IRNTupleAuxDynWriter { + public: + /// Default Constructor + RNTupleAuxDynWriter(); - /// Create a new empty RNTuple row with the current model (fields) - void makeNewEntry(); + /// Default Destructor + virtual ~RNTupleAuxDynWriter() = default; - /// handle writing of dynamic xAOD attributes of an object - called from Container::writeObject() - // throws exceptions - virtual int writeAuxAttributes( const std::string& base_branch, SG::IAuxStoreIO* store, size_t /*rows_written*/ ) override final; + /// Collect Aux data information to be written out + virtual std::vector<attrDataTuple> collectAuxAttributes( + const std::string& base_branch, SG::IAuxStoreIO* store) override final; +}; - /// Add a new field to the RNTuple, collect the data pointer for the commit - void addAttribute( const std::string& field_name, const std::string& attr_type, void* attr_data ); - - /// Add a new field to the RNTuple - virtual void addField( const std::string& field_name, const std::string& attr_type ) override; - - /// Supply data address for a given field - virtual void addFieldValue( const std::string& field_name, void* attr_data ) override; - - virtual int commit() override final; - - virtual const std::string& getName() const override { return m_ntupleName; } - - virtual size_t size() const override { return m_rowN; } - - /// Check if any data needs to be committed - virtual bool needsCommit() const override final { return m_needsCommit; } - - /// Is this RNTuple used by more than one APR container? - virtual bool isGrouped() const override final { return m_clients > 1; } - - /// Keep track of how many APR containers are writing to this RNTuple - virtual void increaseClientCount() override final { m_clients++; } - - virtual void close() override; - - virtual ~RNTupleAuxDynWriter(); - }; - -}// namespace +} // namespace RootAuxDynIO #endif - diff --git a/Database/AthenaRoot/RootAuxDynIO/src/RootAuxDynIO.cxx b/Database/AthenaRoot/RootAuxDynIO/src/RootAuxDynIO.cxx index 1c388fa85abc11859efe76dd8c4640e4101d1a7e..36f0d1399f08d9735418be1081875f1e05615f1d 100644 --- a/Database/AthenaRoot/RootAuxDynIO/src/RootAuxDynIO.cxx +++ b/Database/AthenaRoot/RootAuxDynIO/src/RootAuxDynIO.cxx @@ -96,9 +96,8 @@ namespace RootAuxDynIO return std::make_unique<TBranchAuxDynWriter>(tree, bufferSize, splitLevel, offsettab_len, do_branch_fill); } - std::unique_ptr<RootAuxDynIO::IRNTupleWriter> - getNTupleAuxDynWriter(TFile* file, const std::string& ntupleName, bool enableBufferedWrite, bool enableMetrics) { - return std::make_unique<RNTupleAuxDynWriter>(file, ntupleName, enableBufferedWrite, enableMetrics); + std::unique_ptr<RootAuxDynIO::IRNTupleAuxDynWriter> + getNTupleAuxDynWriter() { + return std::make_unique<RNTupleAuxDynWriter>(); } - }