Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
RNTupleExporter.cxx
Go to the documentation of this file.
1/// \file RNTupleExporter.cxx
2/// \author Giacomo Parolini <giacomo.parolini@cern.ch>
3/// \date 2024-12-10
4/// \warning This is part of the ROOT 7 prototype! It will change without notice. It might trigger earthquakes. Feedback
5/// is welcome!
6
7/*************************************************************************
8 * Copyright (C) 1995-2024, Rene Brun and Fons Rademakers. *
9 * All rights reserved. *
10 * *
11 * For the licensing terms see $ROOTSYS/LICENSE. *
12 * For the list of contributors see $ROOTSYS/README/CREDITS. *
13 *************************************************************************/
14
15#include <ROOT/RError.hxx>
17#include <ROOT/RPageStorage.hxx>
19#include <ROOT/RNTupleZip.hxx>
20#include <ROOT/RClusterPool.hxx>
21#include <ROOT/RLogger.hxx>
22#include <fstream>
23#include <sstream>
24#include <vector>
25
27
28namespace {
29
30ROOT::RLogChannel &RNTupleExporterLog()
31{
32 static RLogChannel sLog("ROOT.RNTupleExporter");
33 return sLog;
34}
35
36struct RColumnExportInfo {
39 std::string fQualifiedName;
40
41 RColumnExportInfo(const ROOT::RNTupleDescriptor &desc, const ROOT::RColumnDescriptor &colDesc,
42 const ROOT::RFieldDescriptor &fieldDesc)
43 : fColDesc(&colDesc),
44 fFieldDesc(&fieldDesc),
45 // NOTE: we don't need to keep the column representation index into account because exactly 1 representation
46 // is active per page, so there is no risk of name collisions.
47 fQualifiedName(desc.GetQualifiedFieldName(fieldDesc.GetId()) + '-' + std::to_string(colDesc.GetIndex()))
48 {
49 }
50};
51
52struct RAddColumnsResult {
53 int fNColsTotal = 0;
54
55 RAddColumnsResult &operator+=(const RAddColumnsResult &other)
56 {
57 fNColsTotal += other.fNColsTotal;
58 return *this;
59 }
60};
61
62template <typename T>
63bool ItemIsFilteredOut(const RNTupleExporter::RFilter<T> &filter, const T &item)
64{
65 bool filterHasType = filter.fSet.find(item) != filter.fSet.end();
66 bool isFiltered = (filter.fType == RNTupleExporter::EFilterType::kBlacklist) == filterHasType;
67 return isFiltered;
68}
69
70RAddColumnsResult AddColumnsFromField(std::vector<RColumnExportInfo> &vec, const ROOT::RNTupleDescriptor &desc,
71 const ROOT::RFieldDescriptor &fieldDesc,
72 const RNTupleExporter::RPagesOptions &options)
73{
74 R__LOG_DEBUG(1, RNTupleExporterLog()) << "processing field \"" << desc.GetQualifiedFieldName(fieldDesc.GetId())
75 << "\"";
76
77 RAddColumnsResult res{};
78
79 for (const auto &subfieldDesc : desc.GetFieldIterable(fieldDesc)) {
80 if (subfieldDesc.IsProjectedField())
81 continue;
82
83 for (const auto &colDesc : desc.GetColumnIterable(subfieldDesc)) {
84 // Filter columns by type
85 bool typeIsFiltered = ItemIsFilteredOut(options.fColumnTypeFilter, colDesc.GetType());
86 if (!typeIsFiltered)
87 vec.emplace_back(desc, colDesc, subfieldDesc);
88 res.fNColsTotal += 1;
89 }
90 res += AddColumnsFromField(vec, desc, subfieldDesc, options);
91 }
92
93 return res;
94}
95
96int CountPages(const ROOT::RNTupleDescriptor &desc, std::span<const RColumnExportInfo> columns)
97{
98 int nPages = 0;
100 for (const auto &clusterDesc : desc.GetActiveClusterIterable()) {
101 for (const auto &colInfo : columns) {
102 const auto &pages = clusterDesc.GetPageRange(colInfo.fColDesc->GetPhysicalId());
103 nPages += pages.GetPageInfos().size();
104 }
105 }
106 return nPages;
107}
108
109} // namespace
110
111RNTupleExporter::RPagesResult
113{
115 throw ROOT::RException(R__FAIL("exporting checksums is incompatible with decompressing the pages"));
116
117 RPagesResult res = {};
118
119 // make sure the source is attached
120 source.Attach();
121
122 auto desc = source.GetSharedDescriptorGuard();
123 ROOT::Internal::RClusterPool clusterPool{source};
124
125 // Collect column info
126 std::vector<RColumnExportInfo> columnInfos;
127 const RAddColumnsResult addColRes = AddColumnsFromField(columnInfos, desc.GetRef(), desc->GetFieldZero(), options);
128
129 // Collect ColumnSet for the cluster pool query
131 columnSet.reserve(columnInfos.size());
132 for (const auto &colInfo : columnInfos) {
133 columnSet.emplace(colInfo.fColDesc->GetPhysicalId());
134 }
135
136 const auto nPages = CountPages(desc.GetRef(), columnInfos);
137
138 const bool showProgress = (options.fFlags & RPagesOptions::kShowProgressBar) != 0;
139 res.fExportedFileNames.reserve(nPages);
140
141 // Iterate over the clusters in order and dump pages
142 R__ASSERT(desc->GetNClusters() == desc->GetNActiveClusters());
143 int pagesExported = 0;
144 int prevIntPercent = 0;
145 std::vector<char> unzipBuf; // Only used when pages get decompressed
146 for (const auto &clusterDesc : desc->GetActiveClusterIterable()) {
147 const auto clusterId = clusterDesc.GetId();
148 const ROOT::Internal::RCluster *cluster = clusterPool.GetCluster(clusterId, columnSet);
149 for (const auto &colInfo : columnInfos) {
150 auto columnId = colInfo.fColDesc->GetPhysicalId();
151 const auto &pages = clusterDesc.GetPageRange(columnId);
152 const auto &colRange = clusterDesc.GetColumnRange(columnId);
153 auto colElement = ROOT::Internal::RColumnElementBase::Generate<void>(colInfo.fColDesc->GetType());
154 colElement->SetBitsOnStorage(colInfo.fColDesc->GetBitsOnStorage());
155
156 std::uint64_t pageIdx = 0;
157
158 R__LOG_DEBUG(0, RNTupleExporterLog())
159 << "exporting column \"" << colInfo.fQualifiedName << "\" (" << pages.GetPageInfos().size() << " pages)";
160
161 // We should never try to export a suppressed column range
162 assert(!colRange.IsSuppressed() || pages.GetPageInfos().empty());
163
164 for (const auto &pageInfo : pages.GetPageInfos()) {
165 ROOT::Internal::ROnDiskPage::Key key{columnId, pageIdx};
166 const ROOT::Internal::ROnDiskPage *onDiskPage = cluster->GetOnDiskPage(key);
167
168 // prepare the output file
169 std::ostringstream ss{options.fOutputPath, std::ios_base::ate};
170 assert(colRange.GetCompressionSettings());
171 ss << "/cluster_" << clusterDesc.GetId() << "_" << colInfo.fQualifiedName << "_page_" << pageIdx
172 << "_elems_" << pageInfo.GetNElements() << "_comp_" << *colRange.GetCompressionSettings() << ".page";
173 const auto outFileName = ss.str();
174 std::ofstream outFile{outFileName, std::ios_base::binary};
175 if (!outFile) {
176 throw ROOT::RException(
177 R__FAIL(std::string("output path ") + options.fOutputPath + " does not exist or is not writable!"));
178 }
179
180 // dump the page
181 const auto *pageBuf = static_cast<const char *>(onDiskPage->GetAddress());
182 if (options.fFlags & RPagesOptions::kDecompress) {
183 const auto nbytesPacked = colElement->GetPackedSize(pageInfo.GetNElements());
184 const auto nbytesData = pageInfo.GetLocator().GetNBytesOnStorage();
185 if (unzipBuf.size() < nbytesPacked)
186 unzipBuf.resize(nbytesPacked);
187 ROOT::Internal::RNTupleDecompressor::Unzip(pageBuf, nbytesData, nbytesPacked, &unzipBuf[0]);
188 outFile.write(unzipBuf.data(), nbytesPacked);
189 } else {
190 const bool includeChecksum =
191 (options.fFlags & RPagesOptions::kIncludeChecksums) != 0 && pageInfo.HasChecksum();
192 const std::size_t maybeChecksumSize = includeChecksum * 8;
193 const auto nbytesData = pageInfo.GetLocator().GetNBytesOnStorage() + maybeChecksumSize;
194 outFile.write(pageBuf, nbytesData);
195 }
196
197 res.fExportedFileNames.push_back(outFileName);
198
199 ++pageIdx, ++pagesExported;
200
201 if (showProgress) {
202 int intPercent = static_cast<int>(100.f * pagesExported / res.fExportedFileNames.size());
203 if (intPercent != prevIntPercent) {
204 fprintf(stderr, "\rExport progress: %02d%%", intPercent);
205 if (intPercent == 100)
206 fprintf(stderr, "\n");
207 prevIntPercent = intPercent;
208 }
209 }
210 }
211 }
212 }
213
214 assert(res.fExportedFileNames.size() == static_cast<size_t>(pagesExported));
215 std::ostringstream ss;
216 ss << "exported " << res.fExportedFileNames.size() << " pages (";
217 if (options.fColumnTypeFilter.fSet.empty()) {
218 ss << addColRes.fNColsTotal << " columns)";
219 } else {
220 auto nColsFilteredOut = addColRes.fNColsTotal - columnInfos.size();
221 ss << nColsFilteredOut << "/" << addColRes.fNColsTotal << " columns filtered out)";
222 }
223 R__LOG_INFO(RNTupleExporterLog()) << ss.str();
224
225 return res;
226}
227
228} // namespace ROOT::Experimental::Internal
#define R__FAIL(msg)
Short-hand to return an RResult<T> in an error state; the RError is implicitly converted into RResult...
Definition RError.hxx:322
#define R__LOG_DEBUG(DEBUGLEVEL,...)
Definition RLogger.hxx:359
#define R__LOG_INFO(...)
Definition RLogger.hxx:358
const ROOT::RFieldDescriptor * fFieldDesc
int fNColsTotal
std::string fQualifiedName
const ROOT::RColumnDescriptor * fColDesc
static void AddColumnsFromField(std::vector< RColumnMergeInfo > &columns, const ROOT::RNTupleDescriptor &srcDesc, const FieldCollectionMap_t< RColReprMapping > &colReprMappings, RNTupleMergeData &mergeData, const ROOT::RFieldDescriptor &srcFieldDesc, const ROOT::RFieldDescriptor &dstFieldDesc, const std::string &prefix="")
#define R__ASSERT(e)
Checks condition e and reports a fatal error if it's false.
Definition TError.h:130
std::string & operator+=(std::string &left, const TString &right)
Definition TString.h:496
@ kBlacklist
Don't export items contained in the filter's set.
static RPagesResult ExportPages(ROOT::Internal::RPageSource &source, const RPagesOptions &options={})
Given a page source, writes all its pages to individual files (1 per page).
Managed a set of clusters containing compressed and packed pages.
An in-memory subset of the packed and compressed pages of a cluster.
Definition RCluster.hxx:147
std::unordered_set< ROOT::DescriptorId_t > ColumnSet_t
Definition RCluster.hxx:149
const ROnDiskPage * GetOnDiskPage(const ROnDiskPage::Key &key) const
Definition RCluster.cxx:30
static void Unzip(const void *from, size_t nbytes, size_t dataLen, void *to)
The nbytes parameter provides the size ls of the from buffer.
A page as being stored on disk, that is packed and compressed.
Definition RCluster.hxx:40
const void * GetAddress() const
Definition RCluster.hxx:62
Abstract interface to read data from an ntuple.
void Attach(ROOT::Internal::RNTupleSerializer::EDescriptorDeserializeMode mode=ROOT::Internal::RNTupleSerializer::EDescriptorDeserializeMode::kForReading)
Open the physical storage container and deserialize header and footer.
RSharedDescriptorGuard GetSharedDescriptorGuard() const
Takes the read lock for the descriptor.
Metadata stored for every column of an RNTuple.
Base class for all ROOT issued exceptions.
Definition RError.hxx:78
Metadata stored for every field of an RNTuple.
ROOT::DescriptorId_t GetId() const
A log configuration for a channel, e.g.
Definition RLogger.hxx:97
The on-storage metadata of an RNTuple.
RClusterDescriptorIterable GetActiveClusterIterable() const
std::size_t GetNActiveClusters() const
std::size_t GetNClusters() const
ROOT::DescriptorId_t R__DEPRECATED(6, 46, "This function is ill-defined in the descriptor " "as not all cluster descriptors may be present. This interface is no longer exposed.") FindClusterId(ROOT ROOT::DescriptorId_t R__DEPRECATED(6, 46, "This function is ill-defined in the descriptor " "as not all cluster descriptors may be present. This interface is no longer exposed.") FindNextClusterId(ROOT ROOT::DescriptorId_t R__DEPRECATED(6, 46, "This function is ill-defined in the descriptor " "as not all cluster descriptors may be present. This interface is no longer exposed.") FindPrevClusterId(ROOT std::string GetQualifiedFieldName(ROOT::DescriptorId_t fieldId) const
Walks up the parents of the field ID and returns a field name of the form a.b.c.d In case of invalid ...
const RFieldDescriptor & GetFieldZero() const
RFilter< ENTupleColumnType > fColumnTypeFilter
Optional filter that determines which columns are included or excluded from being exported.
@ kDecompress
If enabled, uncompress (but don't unpack) the page (mutually exclusive with kIncludeChecksums)
@ kShowProgressBar
If enabled, the exporter will report the current progress on the stderr.
On-disk pages within a page source are identified by the column and page number.
Definition RCluster.hxx:50