Logo ROOT  
Reference Guide
 
Loading...
Searching...
No Matches
RNTupleInspector.cxx
Go to the documentation of this file.
1/// \file RNTupleInspector.cxx
2/// \author Florine de Geus <florine.willemijn.de.geus@cern.ch>
3/// \date 2023-01-09
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-2023, 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
16#include <ROOT/RError.hxx>
20#include "ROOT/RNTupleUtils.hxx"
21
22#include <TFile.h>
23
24#include <algorithm>
25#include <cstring>
26#include <deque>
27#include <exception>
28#include <functional>
29#include <iomanip>
30#include <iostream>
31
33
34ROOT::Experimental::RNTupleInspector::RNTupleInspector(std::unique_ptr<ROOT::Internal::RPageSource> pageSource)
35 : fPageSource(std::move(pageSource))
36{
37 fPageSource->Attach();
38 fPageSource->LoadAllPageLists();
39 auto descriptorGuard = fPageSource->GetSharedDescriptorGuard();
40 fDescriptor = descriptorGuard->Clone();
41
44}
45
46// NOTE: outlined to avoid including RPageStorage in the header
48
50{
51 fCompressedSize = 0;
52 fUncompressedSize = 0;
53
54 std::vector<DescriptorId_t> clusterIds;
55 for (const auto &cgDesc : fDescriptor.GetClusterGroupIterable()) {
56 R__ASSERT(cgDesc.HasClusterDetails());
57 clusterIds.insert(clusterIds.end(), cgDesc.GetClusterIds().begin(), cgDesc.GetClusterIds().end());
58 }
59
60 for (const auto &colDesc : fDescriptor.GetColumnIterable()) {
61 if (colDesc.IsAliasColumn())
62 continue;
63
64 auto colId = colDesc.GetPhysicalId();
65
66 // We generate the default memory representation for the given column type in order
67 // to report the size _in memory_ of column elements.
68 std::uint32_t elemSize = RColumnElementBase::Generate(colDesc.GetType())->GetSize();
69 std::uint64_t nElems = 0;
70 std::vector<std::uint64_t> compressedPageSizes{};
71
72 for (auto cid : clusterIds) {
73 const auto &clusterDescriptor = fDescriptor.GetClusterDescriptor(cid);
74 if (!clusterDescriptor.ContainsColumn(colId)) {
75 continue;
76 }
77
78 auto columnRange = clusterDescriptor.GetColumnRange(colId);
79 if (columnRange.IsSuppressed())
80 continue;
81
82 nElems += columnRange.GetNElements();
83
84 if (!fCompressionSettings && columnRange.GetCompressionSettings()) {
85 fCompressionSettings = columnRange.GetCompressionSettings();
86 } else if (fCompressionSettings && columnRange.GetCompressionSettings() &&
87 (*fCompressionSettings != *columnRange.GetCompressionSettings())) {
88 // Note that currently all clusters and columns are compressed with the same settings and it is not yet
89 // possible to do otherwise. This means that currently, this exception should never be thrown, but this
90 // could change in the future.
91 throw RException(R__FAIL("compression setting mismatch between column ranges (" +
92 std::to_string(*fCompressionSettings) + " vs " +
93 std::to_string(*columnRange.GetCompressionSettings()) +
94 ") for column with physical ID " + std::to_string(colId)));
95 }
96
97 const auto &pageRange = clusterDescriptor.GetPageRange(colId);
98
99 for (const auto &page : pageRange.GetPageInfos()) {
100 compressedPageSizes.emplace_back(page.GetLocator().GetNBytesOnStorage());
101 fUncompressedSize += page.GetNElements() * elemSize;
102 }
103 }
104
105 fCompressedSize +=
106 std::accumulate(compressedPageSizes.begin(), compressedPageSizes.end(), static_cast<std::uint64_t>(0));
107 fColumnInfo.emplace(colId, RColumnInspector(colDesc, compressedPageSizes, elemSize, nElems));
108 }
109}
110
113{
114 std::uint64_t compressedSize = 0;
115 std::uint64_t uncompressedSize = 0;
116
117 for (const auto &colDescriptor : fDescriptor.GetColumnIterable(fieldId)) {
118 auto colInfo = GetColumnInspector(colDescriptor.GetPhysicalId());
119 compressedSize += colInfo.GetCompressedSize();
120 uncompressedSize += colInfo.GetUncompressedSize();
121 }
122
123 for (const auto &subFieldDescriptor : fDescriptor.GetFieldIterable(fieldId)) {
124 auto subFieldId = subFieldDescriptor.GetId();
125
126 auto subFieldInfo = CollectFieldTreeInfo(subFieldId);
127
128 compressedSize += subFieldInfo.GetCompressedSize();
129 uncompressedSize += subFieldInfo.GetUncompressedSize();
130 }
131
132 auto fieldInfo = RFieldTreeInspector(fDescriptor.GetFieldDescriptor(fieldId), compressedSize, uncompressedSize);
133 fFieldTreeInfo.emplace(fieldId, fieldInfo);
134 return fieldInfo;
135}
136
137std::vector<ROOT::DescriptorId_t>
139{
140 std::vector<ROOT::DescriptorId_t> colIds;
141 std::deque<ROOT::DescriptorId_t> fieldIdQueue{fieldId};
142
143 while (!fieldIdQueue.empty()) {
144 auto currId = fieldIdQueue.front();
145 fieldIdQueue.pop_front();
146
147 for (const auto &col : fDescriptor.GetColumnIterable(currId)) {
148 if (col.IsAliasColumn()) {
149 continue;
150 }
151
152 colIds.emplace_back(col.GetPhysicalId());
153 }
154
155 for (const auto &fld : fDescriptor.GetFieldIterable(currId)) {
156 fieldIdQueue.push_back(fld.GetId());
157 }
158 }
159
160 return colIds;
161}
162
163std::unique_ptr<ROOT::Experimental::RNTupleInspector>
165{
166 auto pageSource = ROOT::Internal::RPageSourceFile::CreateFromAnchor(sourceNTuple);
167 return std::unique_ptr<RNTupleInspector>(new RNTupleInspector(std::move(pageSource)));
168}
169
170std::unique_ptr<ROOT::Experimental::RNTupleInspector>
171ROOT::Experimental::RNTupleInspector::Create(std::string_view ntupleName, std::string_view sourceFileName)
172{
173 auto pageSource = ROOT::Internal::RPageSource::Create(ntupleName, sourceFileName);
174 return std::unique_ptr<RNTupleInspector>(new RNTupleInspector(std::move(pageSource)));
175}
176
178{
179 if (!fCompressionSettings)
180 return "unknown";
181
182 int algorithm = *fCompressionSettings / 100;
183 int level = *fCompressionSettings - (algorithm * 100);
184
186 " (level " + std::to_string(level) + ")";
187}
188
189//------------------------------------------------------------------------------
190
193{
194 if (physicalColumnId > fDescriptor.GetNPhysicalColumns()) {
195 throw RException(R__FAIL("No column with physical ID " + std::to_string(physicalColumnId) + " present"));
196 }
197
198 return fColumnInfo.at(physicalColumnId);
199}
200
202{
203 size_t typeCount = 0;
204
205 for (auto &[colId, colInfo] : fColumnInfo) {
206 if (colInfo.GetType() == colType) {
207 ++typeCount;
208 }
209 }
210
211 return typeCount;
212}
213
214std::vector<ROOT::DescriptorId_t>
216{
217 std::vector<ROOT::DescriptorId_t> colIds;
218
219 for (const auto &[colId, colInfo] : fColumnInfo) {
220 if (colInfo.GetType() == colType)
221 colIds.emplace_back(colId);
222 }
223
224 return colIds;
225}
226
227std::vector<ROOT::ENTupleColumnType> ROOT::Experimental::RNTupleInspector::GetColumnTypes()
228{
229 std::set<ROOT::ENTupleColumnType> colTypes;
230
231 for (const auto &[colId, colInfo] : fColumnInfo) {
232 colTypes.emplace(colInfo.GetType());
233 }
234
235 return std::vector(colTypes.begin(), colTypes.end());
236}
237
239{
240 struct ColumnTypeInfo {
241 std::uint64_t nElems = 0;
242 std::uint64_t compressedSize = 0;
243 std::uint64_t uncompressedSize = 0;
244 std::uint64_t nPages = 0;
245 std::uint32_t count = 0;
246
247 void operator+=(const RColumnInspector &colInfo)
248 {
249 this->count++;
250 this->nElems += colInfo.GetNElements();
251 this->compressedSize += colInfo.GetCompressedSize();
252 this->uncompressedSize += colInfo.GetUncompressedSize();
253 this->nPages += colInfo.GetNPages();
254 }
255
256 // Helper method to calculate compression factor
257 float GetCompressionFactor() const
258 {
259 if (compressedSize == 0)
260 return 1.0;
261 return static_cast<float>(uncompressedSize) / static_cast<float>(compressedSize);
262 }
263 };
264
265 std::map<ENTupleColumnType, ColumnTypeInfo> colTypeInfo;
266
267 // Collect information for each column
268 for (const auto &[colId, colInfo] : fColumnInfo) {
269 colTypeInfo[colInfo.GetType()] += colInfo;
270 }
271
272 switch (format) {
274 output << " column type | count | # elements | compressed bytes | uncompressed bytes | compression ratio | "
275 "# pages \n"
276 << "----------------|---------|-------------|------------------|--------------------|-------------------|-"
277 "------\n";
278 for (const auto &[colType, typeInfo] : colTypeInfo)
279 output << std::setw(15) << RColumnElementBase::GetColumnTypeName(colType) << " |" << std::setw(8)
280 << typeInfo.count << " |" << std::setw(12) << typeInfo.nElems << " |" << std::setw(17)
281 << typeInfo.compressedSize << " |" << std::setw(19) << typeInfo.uncompressedSize << " |" << std::fixed
282 << std::setprecision(3) << std::setw(18) << typeInfo.GetCompressionFactor() << " |" << std::setw(6)
283 << typeInfo.nPages << " \n";
284 break;
286 output << "columnType,count,nElements,compressedSize,uncompressedSize,compressionFactor,nPages\n";
287 for (const auto &[colType, typeInfo] : colTypeInfo) {
288 output << RColumnElementBase::GetColumnTypeName(colType) << "," << typeInfo.count << "," << typeInfo.nElems
289 << "," << typeInfo.compressedSize << "," << typeInfo.uncompressedSize << "," << std::fixed
290 << std::setprecision(3) << typeInfo.GetCompressionFactor() << "," << typeInfo.nPages << '\n';
291 }
292 break;
293 default: R__ASSERT(false && "Invalid print format");
294 }
295}
296
297std::unique_ptr<TH1D>
299 std::string_view histName, std::string_view histTitle)
300{
301 if (histName.empty()) {
302 switch (histKind) {
303 case ENTupleInspectorHist::kCount: histName = "colTypeCountHist"; break;
304 case ENTupleInspectorHist::kNElems: histName = "colTypeElemCountHist"; break;
305 case ENTupleInspectorHist::kCompressedSize: histName = "colTypeCompSizeHist"; break;
306 case ENTupleInspectorHist::kUncompressedSize: histName = "colTypeUncompSizeHist"; break;
307 default: throw RException(R__FAIL("Unknown histogram type"));
308 }
309 }
310
311 if (histTitle.empty()) {
312 switch (histKind) {
313 case ENTupleInspectorHist::kCount: histTitle = "Column count by type"; break;
314 case ENTupleInspectorHist::kNElems: histTitle = "Number of elements by column type"; break;
315 case ENTupleInspectorHist::kCompressedSize: histTitle = "Compressed size by column type"; break;
316 case ENTupleInspectorHist::kUncompressedSize: histTitle = "Uncompressed size by column type"; break;
317 default: throw RException(R__FAIL("Unknown histogram type"));
318 }
319 }
320
321 auto hist = std::make_unique<TH1D>(std::string(histName).c_str(), std::string(histTitle).c_str(), 1, 0, 1);
322
323 double data;
324 for (const auto &[colId, colInfo] : fColumnInfo) {
325 switch (histKind) {
326 case ENTupleInspectorHist::kCount: data = 1.; break;
327 case ENTupleInspectorHist::kNElems: data = colInfo.GetNElements(); break;
328 case ENTupleInspectorHist::kCompressedSize: data = colInfo.GetCompressedSize(); break;
329 case ENTupleInspectorHist::kUncompressedSize: data = colInfo.GetUncompressedSize(); break;
330 default: throw RException(R__FAIL("Unknown histogram type"));
331 }
332
333 hist->AddBinContent(hist->GetXaxis()->FindBin(RColumnElementBase::GetColumnTypeName(colInfo.GetType())), data);
334 }
335
336 return hist;
337}
338
339std::unique_ptr<TH1D>
341 std::string histName, std::string histTitle, size_t nBins)
342{
343 if (histTitle.empty())
344 histTitle = "Page size distribution for column with ID " + std::to_string(physicalColumnId);
345
346 return GetPageSizeDistribution({physicalColumnId}, histName, histTitle, nBins);
347}
348
350 std::string histName,
351 std::string histTitle, size_t nBins)
352{
353 if (histName.empty())
354 histName = "pageSizeHistCol" + std::string{RColumnElementBase::GetColumnTypeName(colType)};
355 if (histTitle.empty())
356 histTitle =
357 "Page size distribution for columns with type " + std::string{RColumnElementBase::GetColumnTypeName(colType)};
358
359 auto perTypeHist = GetPageSizeDistribution({colType}, histName, histTitle, nBins);
360
361 if (perTypeHist->GetNhists() < 1)
362 return std::make_unique<TH1D>(histName.c_str(), histTitle.c_str(), 64, 0, 0);
363
364 auto hist = std::unique_ptr<TH1D>(dynamic_cast<TH1D *>(perTypeHist->GetHists()->First()));
365
366 hist->SetName(histName.c_str());
367 hist->SetTitle(histTitle.c_str());
368 hist->SetXTitle("Page size (B)");
369 hist->SetYTitle("N_{pages}");
370 return hist;
371}
372
373std::unique_ptr<TH1D>
374ROOT::Experimental::RNTupleInspector::GetPageSizeDistribution(std::initializer_list<ROOT::DescriptorId_t> colIds,
375 std::string histName, std::string histTitle, size_t nBins)
376{
377 auto hist = std::make_unique<TH1D>();
378
379 if (histName.empty())
380 histName = "pageSizeHist";
381 hist->SetName(histName.c_str());
382 if (histTitle.empty())
383 histTitle = "Page size distribution";
384 hist->SetTitle(histTitle.c_str());
385 hist->SetXTitle("Page size (B)");
386 hist->SetYTitle("N_{pages}");
387
388 std::vector<std::uint64_t> pageSizes;
389 std::for_each(colIds.begin(), colIds.end(), [this, &pageSizes](const auto colId) {
390 auto colInfo = GetColumnInspector(colId);
391 pageSizes.insert(pageSizes.end(), colInfo.GetCompressedPageSizes().begin(),
392 colInfo.GetCompressedPageSizes().end());
393 });
394
395 if (!pageSizes.empty()) {
396 auto histMinMax = std::minmax_element(pageSizes.begin(), pageSizes.end());
397 hist->SetBins(nBins, *histMinMax.first,
398 *histMinMax.second + ((*histMinMax.second - *histMinMax.first) / static_cast<double>(nBins)));
399
400 for (const auto pageSize : pageSizes) {
401 hist->Fill(pageSize);
402 }
403 }
404
405 return hist;
406}
407
408std::unique_ptr<THStack>
409ROOT::Experimental::RNTupleInspector::GetPageSizeDistribution(std::initializer_list<ROOT::ENTupleColumnType> colTypes,
410 std::string histName, std::string histTitle, size_t nBins)
411{
412 if (histName.empty())
413 histName = "pageSizeHist";
414 if (histTitle.empty())
415 histTitle = "Per-column type page size distribution";
416
417 auto stackedHist = std::make_unique<THStack>(histName.c_str(), histTitle.c_str());
418
419 double histMin = std::numeric_limits<double>::max();
420 double histMax = 0;
421 std::map<ROOT::ENTupleColumnType, std::vector<std::uint64_t>> pageSizes;
422
423 std::vector<ROOT::ENTupleColumnType> colTypeVec = colTypes;
424 if (std::empty(colTypes)) {
425 colTypeVec = GetColumnTypes();
426 }
427
428 for (const auto colType : colTypeVec) {
429 auto colIds = GetColumnsByType(colType);
430
431 if (colIds.empty())
432 continue;
433
434 std::vector<std::uint64_t> pageSizesForColType;
435 std::for_each(colIds.cbegin(), colIds.cend(), [this, &pageSizesForColType](const auto colId) {
436 auto colInfo = GetColumnInspector(colId);
437 pageSizesForColType.insert(pageSizesForColType.end(), colInfo.GetCompressedPageSizes().begin(),
438 colInfo.GetCompressedPageSizes().end());
439 });
440 if (pageSizesForColType.empty())
441 continue;
442
443 pageSizes.emplace(colType, pageSizesForColType);
444
445 auto histMinMax = std::minmax_element(pageSizesForColType.begin(), pageSizesForColType.end());
446 histMin = std::min(histMin, static_cast<double>(*histMinMax.first));
447 histMax = std::max(histMax, static_cast<double>(*histMinMax.second));
448 }
449
450 for (const auto &[colType, pageSizesForColType] : pageSizes) {
451 auto hist = std::make_unique<TH1D>(
452 TString::Format("%s%s", histName.c_str(), RColumnElementBase::GetColumnTypeName(colType)),
453 RColumnElementBase::GetColumnTypeName(colType), nBins, histMin,
454 histMax + ((histMax - histMin) / static_cast<double>(nBins)));
455
456 for (const auto pageSize : pageSizesForColType) {
457 hist->Fill(pageSize);
458 }
459
460 stackedHist->Add(hist.release());
461 }
462
463 return stackedHist;
464}
465
467 std::initializer_list<ROOT::ENTupleColumnType> colTypes, std::string_view histName, std::string_view histTitle,
468 size_t nBins)
469{
470 if (histName.empty())
471 histName = "pagesPerClusterHist";
472 if (histTitle.empty())
473 histTitle = "#pages per cluster";
474
475 auto stackedHist = std::make_unique<THStack>(std::string(histName).c_str(), std::string(histTitle).c_str());
476 // Each element of the vector contains the sum of all pages of all columns of each type.
477 std::vector<std::unordered_map<ENTupleColumnType, std::uint64_t>> pagesPerClusterPerColumnType;
478
479 std::vector<ROOT::ENTupleColumnType> colTypeVec = colTypes;
480 if (std::empty(colTypes)) {
481 colTypeVec = GetColumnTypes();
482 }
483
484 R__ASSERT(fDescriptor.GetNClusters() == fDescriptor.GetNActiveClusters());
485 for (const auto &clDesc : fDescriptor.GetActiveClusterIterable()) {
486 auto &pagesPerColumnTypeInThisCluster = pagesPerClusterPerColumnType.emplace_back();
487 for (const auto &colRange : clDesc.GetColumnRangeIterable()) {
488 const auto colId = colRange.GetPhysicalColumnId();
489 const auto &colDesc = fDescriptor.GetColumnDescriptor(colId);
490 const auto nPages = clDesc.GetPageRange(colId).GetPageInfos().size();
491 auto &nPagesForThisType = pagesPerColumnTypeInThisCluster[colDesc.GetType()];
492 nPagesForThisType += nPages;
493 }
494 }
495
496 double histMin = std::numeric_limits<double>::max();
497 double histMax = 0;
498 for (const auto &pagesPerType : pagesPerClusterPerColumnType) {
499 auto [min, max] = std::minmax_element(pagesPerType.begin(), pagesPerType.end(),
500 [](const auto &a, const auto &b) { return a.second < b.second; });
501 histMin = std::min<double>(histMin, min->second);
502 histMax = std::max<double>(histMax, max->second);
503 }
504
505 std::array<std::unique_ptr<TH1L>, static_cast<std::size_t>(ENTupleColumnType::kMax)> histsPerType = {};
506 for (const auto &pagesPerType : pagesPerClusterPerColumnType) {
507 for (const auto &[colType, nPages] : pagesPerType) {
508 auto &hist = histsPerType[static_cast<std::size_t>(colType)];
509 if (!hist) {
510 hist = std::make_unique<TH1L>(ROOT::Internal::RColumnElementBase::GetColumnTypeName(colType),
512 histMin, histMax + ((histMax - histMin) / static_cast<double>(nBins)));
513 }
514 hist->Fill(nPages);
515 }
516 }
517
518 for (auto &hist : histsPerType) {
519 if (hist) {
520 stackedHist->Add(hist.release());
521 }
522 }
523
524 return stackedHist;
525}
526
527//------------------------------------------------------------------------------
528
531{
532 if (fieldId >= fDescriptor.GetNFields()) {
533 throw RException(R__FAIL("No field with ID " + std::to_string(fieldId) + " present"));
534 }
535
536 return fFieldTreeInfo.at(fieldId);
537}
538
541{
542 auto fieldId = fDescriptor.FindFieldId(fieldName);
543
544 if (fieldId == kInvalidDescriptorId) {
545 throw RException(R__FAIL("Could not find field `" + std::string(fieldName) + "`"));
546 }
547
548 return GetFieldTreeInspector(fieldId);
549}
550
551size_t ROOT::Experimental::RNTupleInspector::GetFieldCountByType(const std::regex &typeNamePattern,
552 bool includeSubfields) const
553{
554 size_t typeCount = 0;
555
556 for (auto &[fldId, fldInfo] : fFieldTreeInfo) {
557 if (!includeSubfields && fldInfo.GetDescriptor().GetParentId() != fDescriptor.GetFieldZeroId()) {
558 continue;
559 }
560
561 if (std::regex_match(fldInfo.GetDescriptor().GetTypeName(), typeNamePattern)) {
562 typeCount++;
563 }
564 }
565
566 return typeCount;
567}
568
569std::vector<ROOT::DescriptorId_t>
570ROOT::Experimental::RNTupleInspector::GetFieldsByName(const std::regex &fieldNamePattern, bool searchInSubfields) const
571{
572 std::vector<ROOT::DescriptorId_t> fieldIds;
573
574 for (auto &[fldId, fldInfo] : fFieldTreeInfo) {
575
576 if (!searchInSubfields && fldInfo.GetDescriptor().GetParentId() != fDescriptor.GetFieldZeroId()) {
577 continue;
578 }
579
580 if (std::regex_match(fldInfo.GetDescriptor().GetFieldName(), fieldNamePattern)) {
581 fieldIds.emplace_back(fldId);
582 }
583 }
584
585 return fieldIds;
586}
587
589 std::ostream &output) const
590{
591 const auto &tupleDescriptor = GetDescriptor();
592 const bool isZeroField = fieldDescriptor.GetParentId() == ROOT::kInvalidDescriptorId;
593 if (isZeroField) {
594 output << "digraph D {\n";
595 output << "node[shape=box]\n";
596 }
597 const std::string &nodeId = (isZeroField) ? "0" : std::to_string(fieldDescriptor.GetId() + 1);
598 const std::string &description = fieldDescriptor.GetFieldDescription();
599 const std::uint32_t &version = fieldDescriptor.GetFieldVersion();
600
601 auto htmlEscape = [&](const std::string &in) -> std::string {
602 std::string out;
603 out.reserve(in.size());
604 for (const char &c : in) {
605 switch (c) {
606 case '&': out += "&amp;"; break;
607 case '<': out += "&lt;"; break;
608 case '>': out += "&gt;"; break;
609 case '\"': out += "&quot;"; break;
610 case '\'': out += "&#39;"; break;
611 default: out += c; break;
612 }
613 }
614 return out;
615 };
616
617 output << nodeId << "[label=<";
618 if (!isZeroField) {
619 output << "<b>Name: </b>" << htmlEscape(fieldDescriptor.GetFieldName()) << "<br></br>";
620 output << "<b>Type: </b>" << htmlEscape(fieldDescriptor.GetTypeName()) << "<br></br>";
621 output << "<b>ID: </b>" << std::to_string(fieldDescriptor.GetId()) << "<br></br>";
622 if (description != "")
623 output << "<b>Description: </b>" << htmlEscape(description) << "<br></br>";
624 if (version != 0)
625 output << "<b>Version: </b>" << version << "<br></br>";
626 } else
627 output << "<b>RFieldZero</b>";
628 output << ">]\n";
629 for (const auto &childFieldId : fieldDescriptor.GetLinkIds()) {
630 const auto &childFieldDescriptor = tupleDescriptor.GetFieldDescriptor(childFieldId);
631 output << nodeId + "->" + std::to_string(childFieldDescriptor.GetId() + 1) + "\n";
632 PrintFieldTreeAsDot(childFieldDescriptor, output);
633 }
634 if (isZeroField)
635 output << "}";
636}
637
638namespace {
639
640struct SpeedscopeFrame {
641 std::string fString;
642 std::uint64_t fOpeningPosition = 0;
643 std::uint64_t fClosingPosition = 0;
644};
645
646static void PrintSpeedscopeFrames(const std::vector<SpeedscopeFrame> &frames, std::ostream &output)
647{
648 output << "{\n";
649 output << " \"$schema\":\"https://www.speedscope.app/file-format-schema.json\",\n";
650 output << " \"shared\":{\n";
651 output << " \"frames\":[\n";
652
653 for (std::size_t i = 0; i < frames.size(); ++i) {
654 output << " { \"name\":\"" << frames[i].fString << "\" }" << (i + 1 < frames.size() ? ",\n" : "\n");
655 }
656
657 output << " ]\n";
658 output << " },\n";
659 output << " \"profiles\":[\n";
660 output << " {\n";
661 output << " \"type\":\"evented\",\n";
662 output << " \"name\":\"Flattened Timeline\",\n";
663 output << " \"unit\":\"bytes\",\n";
664 output << " \"startValue\":0,\n";
665 output << " \"endValue\":" << frames.back().fClosingPosition << ",\n";
666 output << " \"events\":[\n";
667
668 bool first = true;
669
670 // Parameter idx Index of the frame being processed
671 // Parameter limit
672 // - If the frame is not root: Closing Position of its father
673 // - If the frame is root: Closing Position of the last element of frames
674 // Returns the next index to be processed
675 std::function<std::size_t(std::size_t, std::uint32_t)> processRecursive = [&](std::size_t nextIdxToProcess,
676 std::uint32_t limit) -> std::size_t {
677 while (nextIdxToProcess < frames.size() && frames[nextIdxToProcess].fOpeningPosition < limit) {
678 const std::size_t currentIdx = nextIdxToProcess;
679
680 if (!first)
681 output << ",\n";
682
683 output << " {\"type\":\"O\",\"frame\":" << currentIdx
684 << ",\"at\":" << frames[currentIdx].fOpeningPosition << "}";
685 first = false;
686
687 nextIdxToProcess = processRecursive(nextIdxToProcess + 1, frames[currentIdx].fClosingPosition);
688
689 output << ",\n {\"type\":\"C\",\"frame\":" << currentIdx
690 << ",\"at\":" << frames[currentIdx].fClosingPosition << "}";
691 }
692 return nextIdxToProcess;
693 };
694
695 processRecursive(0, frames.back().fClosingPosition);
696
697 output << "\n ]\n";
698 output << " }\n";
699 output << " ]\n";
700 output << "}\n";
701}
702} // namespace
703
705 [[maybe_unused]] ESchemaProfileFormat format) const
706{
707 // There is only one format at the moment
709
710 const auto &tupleDescriptor = GetDescriptor();
711 ROOT::DescriptorId_t rootId = tupleDescriptor.GetFieldZeroId();
712 const auto &rootFieldDescriptor = tupleDescriptor.GetFieldDescriptor(rootId);
713
714 std::vector<SpeedscopeFrame> frames;
715 std::uint64_t positionCursor = 0;
716
717 // Returns size of the visited field
718 auto visitFieldsRecursive = [&](auto &self, const ROOT::RFieldDescriptor &fieldDescriptor) -> std::size_t {
719 SpeedscopeFrame fieldSpeedscopeFrame;
720 fieldSpeedscopeFrame.fString =
721 tupleDescriptor.GetQualifiedFieldName(fieldDescriptor.GetId()) + " (" + fieldDescriptor.GetTypeName() + ")";
722 fieldSpeedscopeFrame.fOpeningPosition = positionCursor;
723 frames.push_back(fieldSpeedscopeFrame);
724
725 std::size_t fieldSpeedscopeFrameIndex = frames.size() - 1;
726
727 std::size_t subTreeSize = 0;
728 const auto &childIds = fieldDescriptor.GetLinkIds();
729
730 for (const auto &childFieldId : childIds) {
731 const auto &childFieldDescriptor = tupleDescriptor.GetFieldDescriptor(childFieldId);
732 subTreeSize += self(self, childFieldDescriptor);
733 }
734
735 for (const auto &columnDescriptor : tupleDescriptor.GetColumnIterable(fieldDescriptor.GetId())) {
736 const auto &columnInfo = GetColumnInspector(columnDescriptor.GetPhysicalId());
737 std::size_t columnSize = columnInfo.GetCompressedSize();
738
739 SpeedscopeFrame columnSpeedscopeFrame;
740 columnSpeedscopeFrame.fString =
741 "[col#" + std::to_string(columnDescriptor.GetPhysicalId()) + "] " +
742 tupleDescriptor.GetQualifiedFieldName(fieldDescriptor.GetId()) + " (" +
743 ROOT::Internal::RColumnElementBase::GetColumnTypeName(columnDescriptor.GetType()) + ")";
744 columnSpeedscopeFrame.fOpeningPosition = positionCursor;
745 positionCursor += columnSize;
746 columnSpeedscopeFrame.fClosingPosition = positionCursor;
747 frames.push_back(columnSpeedscopeFrame);
748 subTreeSize += columnSize;
749 }
750
751 frames[fieldSpeedscopeFrameIndex].fClosingPosition = positionCursor;
752
753 return subTreeSize;
754 };
755
756 const auto &topLevelIds = rootFieldDescriptor.GetLinkIds();
757 for (const auto &childId : topLevelIds) {
758 const auto &childFieldDescriptor = tupleDescriptor.GetFieldDescriptor(childId);
759 visitFieldsRecursive(visitFieldsRecursive, childFieldDescriptor);
760 }
761
762 PrintSpeedscopeFrames(frames, output);
763}
764
766 [[maybe_unused]] ESchemaProfileFormat format) const
767{
768 // There is only one format at the moment
770
771 const auto *pageSourceFile = dynamic_cast<const ROOT::Internal::RPageSourceFile *>(fPageSource.get());
772 // GetAnchorFromFile() only supports file-based backend, so better to check early
773 if (!pageSourceFile)
774 throw RException(R__FAIL("Disk profile is only supported for file-based page sources"));
775 const auto anchor = ROOT::Internal::GetAnchorFromFile(*pageSourceFile);
776 if (!anchor)
777 R__LOG_WARNING(ROOT::Internal::NTupleLog()) << "Cannot retrieve RNTuple anchor";
778
779 const auto &descriptor = GetDescriptor();
780
781 struct RDiskPageLeaf {
782 std::uint64_t fPosition = 0;
783 std::uint64_t fSize = 0;
784 std::string fName;
785 std::array<DescriptorId_t, 3> fAncestors;
786 };
787 static constexpr std::array<const char *, 3> kAncestorsNames = {"cluster group", "cluster", "column range"};
788 std::vector<RDiskPageLeaf> pageLeaves;
789
790 // Collect all pageLeaves in whichever order the iterator provides
791 for (const auto &clusterGroupDescriptor : descriptor.GetClusterGroupIterable()) {
792 const auto groupId = clusterGroupDescriptor.GetId();
793
794 for (const auto clusterId : clusterGroupDescriptor.GetClusterIds()) {
795 const auto &clusterDescriptor = descriptor.GetClusterDescriptor(clusterId);
796
797 for (const auto &columnRange : clusterDescriptor.GetColumnRangeIterable()) {
798 const auto columnId = columnRange.GetPhysicalColumnId();
799
800 const auto &pageRange = clusterDescriptor.GetPageRange(columnId);
801 for (const auto &pageInfo : pageRange.GetPageInfos()) {
802 const auto &locator = pageInfo.GetLocator();
803
804 RDiskPageLeaf pageLeaf;
805 pageLeaf.fPosition = locator.GetPosition<std::uint64_t>();
806 pageLeaf.fSize = locator.GetNBytesOnStorage() +
808 pageLeaf.fName = "[page @" + std::to_string(pageLeaf.fPosition) + "]";
809 pageLeaf.fAncestors = {groupId, clusterId, columnId};
810 pageLeaves.push_back(pageLeaf);
811 }
812 }
813 }
814 }
815
816 // Sort pageLeafs by on-disk address
817 std::sort(pageLeaves.begin(), pageLeaves.end(),
818 [](const RDiskPageLeaf &a, const RDiskPageLeaf &b) { return a.fPosition < b.fPosition; });
819
820 // Remove aliases (the ntuple specification allows complete, but not partial, overlap between pages)
821 pageLeaves.erase(
822 std::unique(pageLeaves.begin(), pageLeaves.end(),
823 [](const RDiskPageLeaf &a, const RDiskPageLeaf &b) { return a.fPosition == b.fPosition; }),
824 pageLeaves.end());
825
826 std::vector<SpeedscopeFrame> frames;
827
828 // Construct frame for ntuple header
829 if (anchor) {
830 SpeedscopeFrame headerFrame;
831 headerFrame.fString = "ntuple header";
832 headerFrame.fOpeningPosition = anchor->GetSeekHeader();
833 headerFrame.fClosingPosition = anchor->GetSeekHeader() + anchor->GetNBytesHeader();
834 frames.push_back(headerFrame);
835 }
836
837 struct ROpenFrame {
838 ROOT::DescriptorId_t fId = 0; // clusterGroup, cluster, columnRange id
839 std::size_t fIndex = 0; // index in frames vector
840 };
841 std::vector<ROpenFrame> openFrames;
842 std::uint64_t previouspageLeafEnd = 0;
843
844 // Construct frames from the bottom (leafs ordered by disk address) upwards
845 for (const auto &pageLeaf : pageLeaves) {
846 std::size_t sharedDepth = 0;
847
848 // How many of the currently open ancestors does this pageLeaf share?
849 while (sharedDepth < openFrames.size() && sharedDepth < pageLeaf.fAncestors.size() &&
850 openFrames[sharedDepth].fId == pageLeaf.fAncestors[sharedDepth]) {
851 sharedDepth++;
852 }
853
854 // Close ancestors not shared with this pageLeaf (innermost first order)
855 while (openFrames.size() > sharedDepth) {
856 frames[openFrames.back().fIndex].fClosingPosition = previouspageLeafEnd;
857 openFrames.pop_back();
858 }
859
860 // Open the ancestors this pageLeaf needs (outermost first order)
861 for (std::size_t depth = sharedDepth; depth < pageLeaf.fAncestors.size(); ++depth) {
862 SpeedscopeFrame ancestorFrame;
863 ancestorFrame.fString =
864 "[" + std::string(kAncestorsNames[depth]) + " " + std::to_string(pageLeaf.fAncestors[depth]) + "]";
865 ancestorFrame.fOpeningPosition = pageLeaf.fPosition;
866 frames.push_back(ancestorFrame);
867
868 ROpenFrame openFrame;
869 openFrame.fId = pageLeaf.fAncestors[depth];
870 openFrame.fIndex = frames.size() - 1;
871 openFrames.push_back(openFrame);
872 }
873
874 // Emit the pageLeaf itself
875 SpeedscopeFrame pageLeafFrame;
876 pageLeafFrame.fString = pageLeaf.fName;
877 pageLeafFrame.fOpeningPosition = pageLeaf.fPosition;
878 pageLeafFrame.fClosingPosition = pageLeaf.fPosition + pageLeaf.fSize;
879 frames.push_back(pageLeafFrame);
880
881 previouspageLeafEnd = pageLeaf.fPosition + pageLeaf.fSize;
882 }
883
884 // Close whatever is still open after the last pageLeaf
885 while (!openFrames.empty()) {
886 frames[openFrames.back().fIndex].fClosingPosition = previouspageLeafEnd;
887 openFrames.pop_back();
888 }
889
890 // Construct frames for page lists
891 for (const auto &clusterGroupDescriptor : descriptor.GetClusterGroupIterable()) {
892 const auto locator = clusterGroupDescriptor.GetPageListLocator();
893
894 SpeedscopeFrame pageListFrame;
895 pageListFrame.fString = "[page list " + std::to_string(clusterGroupDescriptor.GetId()) + "]";
896 pageListFrame.fOpeningPosition = locator.GetPosition<std::uint64_t>();
897 pageListFrame.fClosingPosition = locator.GetPosition<std::uint64_t>() + locator.GetNBytesOnStorage();
898 frames.push_back(pageListFrame);
899 }
900
901 // Construct frame for ntuple footer
902 if (anchor) {
903 SpeedscopeFrame footerFrame;
904 footerFrame.fString = "ntuple footer";
905 footerFrame.fOpeningPosition = anchor->GetSeekFooter();
906 footerFrame.fClosingPosition = anchor->GetSeekFooter() + anchor->GetNBytesFooter();
907 frames.push_back(footerFrame);
908 }
909
910 PrintSpeedscopeFrames(frames, output);
911}
dim_t fSize
#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_WARNING(...)
Definition RLogger.hxx:357
#define b(i)
Definition RSha256.hxx:100
#define c(i)
Definition RSha256.hxx:101
#define a(i)
Definition RSha256.hxx:99
#define R__ASSERT(e)
Checks condition e and reports a fatal error if it's false.
Definition TError.h:139
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void data
Option_t Option_t TPoint TPoint const char GetTextMagnitude GetFillStyle GetLineColor GetLineWidth GetMarkerStyle GetTextAlign GetTextColor GetTextSize void char Point_t Rectangle_t WindowAttributes_t Float_t Float_t Float_t Int_t Int_t UInt_t UInt_t Rectangle_t Int_t Int_t Window_t TString Int_t GCValues_t GetPrimarySelectionOwner GetDisplay GetScreen GetColormap GetNativeEvent const char const char dpyName wid window const char font_name cursor keysym reg const char only_if_exist regb h Point_t winding char text const char depth char const char Int_t count const char ColorStruct_t color const char Pixmap_t Pixmap_t PictureAttributes_t attr const char char ret_data h unsigned char height h Atom_t Int_t ULong_t ULong_t unsigned char prop_list Atom_t Atom_t Atom_t Time_t format
std::string & operator+=(std::string &left, const TString &right)
Definition TString.h:496
The available trivial, native content types of a column.
Provides column-level storage information.
Inspect on-disk and storage-related information of an RNTuple.
std::vector< ROOT::DescriptorId_t > GetFieldsByName(const std::regex &fieldNamePattern, bool searchInSubfields=true) const
Get the IDs of (sub-)fields whose name matches the given string.
const RFieldTreeInspector & GetFieldTreeInspector(ROOT::DescriptorId_t fieldId) const
Get storage information for a given (sub)field by ID.
std::unique_ptr< TH1D > GetPageSizeDistribution(ROOT::DescriptorId_t physicalColumnId, std::string histName="", std::string histTitle="", size_t nBins=64)
Get a histogram containing the size distribution of the compressed pages for an individual column.
size_t GetColumnCountByType(ROOT::ENTupleColumnType colType) const
Get the number of columns of a given type present in the RNTuple.
std::vector< ROOT::ENTupleColumnType > GetColumnTypes()
Get all column types present in the RNTuple being inspected.
size_t GetFieldCountByType(const std::regex &typeNamePattern, bool searchInSubfields=true) const
Get the number of fields of a given type or class present in the RNTuple.
void PrintSchemaProfile(std::ostream &output=std::cout, ESchemaProfileFormat format=ESchemaProfileFormat::kSpeedscopeJSON) const
Print a string that represents the tree of the (sub)fields and columns of an RNTuple in a format whic...
std::vector< ROOT::DescriptorId_t > GetColumnsByType(ROOT::ENTupleColumnType colType)
Get the IDs of all columns with the given type.
std::string GetCompressionSettingsAsString() const
Get a string describing compression settings of the RNTuple being inspected.
RFieldTreeInspector CollectFieldTreeInfo(ROOT::DescriptorId_t fieldId)
Recursively gather field-level information.
RNTupleInspector(std::unique_ptr< ROOT::Internal::RPageSource > pageSource)
void PrintColumnTypeInfo(ENTupleInspectorPrintFormat format=ENTupleInspectorPrintFormat::kTable, std::ostream &output=std::cout)
Print storage information per column type.
std::unique_ptr< THStack > GetPagesPerClusterDistribution(std::initializer_list< ROOT::ENTupleColumnType > colTypes={}, std::string_view histName="", std::string_view histTitle="", size_t nBins=32)
void PrintDiskProfile(std::ostream &output=std::cout, ESchemaProfileFormat format=ESchemaProfileFormat::kSpeedscopeJSON) const
Print a string that represents the on-disk storage of the cluster groups, clusters,...
const RColumnInspector & GetColumnInspector(ROOT::DescriptorId_t physicalColumnId) const
Get storage information for a given column.
std::unique_ptr< ROOT::Internal::RPageSource > fPageSource
static std::unique_ptr< RNTupleInspector > Create(const RNTuple &sourceNTuple)
Create a new RNTupleInspector.
void CollectColumnInfo()
Gather column-level and RNTuple-level information.
void PrintFieldTreeAsDot(const ROOT::RFieldDescriptor &fieldDescriptor, std::ostream &output=std::cout) const
Print a .dot string that represents the tree of the (sub)fields of an RNTuple.
std::vector< ROOT::DescriptorId_t > GetAllColumnsOfField(ROOT::DescriptorId_t fieldId) const
Get the columns that make up the given field, including its subfields.
std::unique_ptr< TH1D > GetColumnTypeInfoAsHist(ENTupleInspectorHist histKind, std::string_view histName="", std::string_view histTitle="")
Get a histogram showing information for each column type present,.
A column element encapsulates the translation between basic C++ types and their column representation...
static const char * GetColumnTypeName(ROOT::ENTupleColumnType type)
static std::unique_ptr< RColumnElementBase > Generate(ROOT::ENTupleColumnType type)
If CppT == void, use the default C++ type for the given column type.
Storage provider that reads ntuple pages from a file.
static std::unique_ptr< RPageSourceFile > CreateFromAnchor(const RNTuple &anchor, const ROOT::RNTupleReadOptions &options=ROOT::RNTupleReadOptions())
Used from the RNTuple class to build a datasource if the anchor is already available.
static std::unique_ptr< RPageSource > Create(std::string_view ntupleName, std::string_view location, const ROOT::RNTupleReadOptions &options=ROOT::RNTupleReadOptions())
Guess the concrete derived page source from the file name (location)
static constexpr std::size_t kNBytesPageChecksum
The page checksum is a 64bit xxhash3.
Base class for all ROOT issued exceptions.
Definition RError.hxx:78
Metadata stored for every field of an RNTuple.
ROOT::DescriptorId_t GetId() const
std::uint32_t GetFieldVersion() const
const std::vector< ROOT::DescriptorId_t > & GetLinkIds() const
ROOT::DescriptorId_t GetParentId() const
const std::string & GetFieldDescription() const
const std::string & GetFieldName() const
const std::string & GetTypeName() const
ROOT::NTupleSize_t R__DEPRECATED(6, 46, "This function is ill-conceived in the descriptor " "as not all cluster descriptors may be present. This interface is no longer publicly exposed.") GetNElements(ROOT ROOT::DescriptorId_t GetFieldZeroId() const
Returns the logical parent of all top-level RNTuple data fields.
RNTupleDescriptor Clone() const
Representation of an RNTuple data set in a ROOT file.
Definition RNTuple.hxx:67
1-D histogram with a double per channel (see TH1 documentation)
Definition TH1.h:926
static TString Format(const char *fmt,...)
Static method which formats a string using a printf style format descriptor and return a TString.
Definition TString.cxx:2460
@ kSpeedscopeJSON
https://www.speedscope.app/file-format-schema.json
ROOT::RLogChannel & NTupleLog()
Log channel for RNTuple diagnostics.
const ROOT::RNTuple * GetAnchorFromFile(const RPageSourceFile &source)
std::uint64_t DescriptorId_t
Distriniguishes elements of the same type within a descriptor, e.g. different fields.
constexpr DescriptorId_t kInvalidDescriptorId
EValues
Note: this is only temporarily a struct and will become a enum class hence the name convention used.
Definition Compression.h:88
static std::string AlgorithmToString(EAlgorithm::EValues algorithm)