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 auto descriptorGuard = fPageSource->GetSharedDescriptorGuard();
40
43}
44
45// NOTE: outlined to avoid including RPageStorage in the header
47
49{
50 fCompressedSize = 0;
51 fUncompressedSize = 0;
52
53 for (const auto &colDesc : fDescriptor.GetColumnIterable()) {
54 if (colDesc.IsAliasColumn())
55 continue;
56
57 auto colId = colDesc.GetPhysicalId();
58
59 // We generate the default memory representation for the given column type in order
60 // to report the size _in memory_ of column elements.
61 std::uint32_t elemSize = RColumnElementBase::Generate(colDesc.GetType())->GetSize();
62 std::uint64_t nElems = 0;
63 std::vector<std::uint64_t> compressedPageSizes{};
64
65 for (const auto &clusterDescriptor : fDescriptor.GetClusterIterable()) {
66 if (!clusterDescriptor.ContainsColumn(colId)) {
67 continue;
68 }
69
70 auto columnRange = clusterDescriptor.GetColumnRange(colId);
71 if (columnRange.IsSuppressed())
72 continue;
73
74 nElems += columnRange.GetNElements();
75
76 if (!fCompressionSettings && columnRange.GetCompressionSettings()) {
77 fCompressionSettings = columnRange.GetCompressionSettings();
78 } else if (fCompressionSettings && columnRange.GetCompressionSettings() &&
79 (*fCompressionSettings != *columnRange.GetCompressionSettings())) {
80 // Note that currently all clusters and columns are compressed with the same settings and it is not yet
81 // possible to do otherwise. This means that currently, this exception should never be thrown, but this
82 // could change in the future.
83 throw RException(R__FAIL("compression setting mismatch between column ranges (" +
84 std::to_string(*fCompressionSettings) + " vs " +
85 std::to_string(*columnRange.GetCompressionSettings()) +
86 ") for column with physical ID " + std::to_string(colId)));
87 }
88
89 const auto &pageRange = clusterDescriptor.GetPageRange(colId);
90
91 for (const auto &page : pageRange.GetPageInfos()) {
92 compressedPageSizes.emplace_back(page.GetLocator().GetNBytesOnStorage());
93 fUncompressedSize += page.GetNElements() * elemSize;
94 }
95 }
96
97 fCompressedSize +=
98 std::accumulate(compressedPageSizes.begin(), compressedPageSizes.end(), static_cast<std::uint64_t>(0));
100 }
101}
102
105{
106 std::uint64_t compressedSize = 0;
107 std::uint64_t uncompressedSize = 0;
108
109 for (const auto &colDescriptor : fDescriptor.GetColumnIterable(fieldId)) {
110 auto colInfo = GetColumnInspector(colDescriptor.GetPhysicalId());
111 compressedSize += colInfo.GetCompressedSize();
112 uncompressedSize += colInfo.GetUncompressedSize();
113 }
114
115 for (const auto &subFieldDescriptor : fDescriptor.GetFieldIterable(fieldId)) {
116 auto subFieldId = subFieldDescriptor.GetId();
117
118 auto subFieldInfo = CollectFieldTreeInfo(subFieldId);
119
120 compressedSize += subFieldInfo.GetCompressedSize();
121 uncompressedSize += subFieldInfo.GetUncompressedSize();
122 }
123
124 auto fieldInfo = RFieldTreeInspector(fDescriptor.GetFieldDescriptor(fieldId), compressedSize, uncompressedSize);
125 fFieldTreeInfo.emplace(fieldId, fieldInfo);
126 return fieldInfo;
127}
128
129std::vector<ROOT::DescriptorId_t>
131{
132 std::vector<ROOT::DescriptorId_t> colIds;
133 std::deque<ROOT::DescriptorId_t> fieldIdQueue{fieldId};
134
135 while (!fieldIdQueue.empty()) {
136 auto currId = fieldIdQueue.front();
137 fieldIdQueue.pop_front();
138
139 for (const auto &col : fDescriptor.GetColumnIterable(currId)) {
140 if (col.IsAliasColumn()) {
141 continue;
142 }
143
144 colIds.emplace_back(col.GetPhysicalId());
145 }
146
147 for (const auto &fld : fDescriptor.GetFieldIterable(currId)) {
148 fieldIdQueue.push_back(fld.GetId());
149 }
150 }
151
152 return colIds;
153}
154
155std::unique_ptr<ROOT::Experimental::RNTupleInspector>
161
162std::unique_ptr<ROOT::Experimental::RNTupleInspector>
164{
166 return std::unique_ptr<RNTupleInspector>(new RNTupleInspector(std::move(pageSource)));
167}
168
170{
171 if (!fCompressionSettings)
172 return "unknown";
173
174 int algorithm = *fCompressionSettings / 100;
175 int level = *fCompressionSettings - (algorithm * 100);
176
178 " (level " + std::to_string(level) + ")";
179}
180
181//------------------------------------------------------------------------------
182
185{
186 if (physicalColumnId > fDescriptor.GetNPhysicalColumns()) {
187 throw RException(R__FAIL("No column with physical ID " + std::to_string(physicalColumnId) + " present"));
188 }
189
190 return fColumnInfo.at(physicalColumnId);
191}
192
194{
195 size_t typeCount = 0;
196
197 for (auto &[colId, colInfo] : fColumnInfo) {
198 if (colInfo.GetType() == colType) {
199 ++typeCount;
200 }
201 }
202
203 return typeCount;
204}
205
206std::vector<ROOT::DescriptorId_t>
208{
209 std::vector<ROOT::DescriptorId_t> colIds;
210
211 for (const auto &[colId, colInfo] : fColumnInfo) {
212 if (colInfo.GetType() == colType)
213 colIds.emplace_back(colId);
214 }
215
216 return colIds;
217}
218
219std::vector<ROOT::ENTupleColumnType> ROOT::Experimental::RNTupleInspector::GetColumnTypes()
220{
221 std::set<ROOT::ENTupleColumnType> colTypes;
222
223 for (const auto &[colId, colInfo] : fColumnInfo) {
224 colTypes.emplace(colInfo.GetType());
225 }
226
227 return std::vector(colTypes.begin(), colTypes.end());
228}
229
231{
232 struct ColumnTypeInfo {
233 std::uint64_t nElems = 0;
234 std::uint64_t compressedSize = 0;
235 std::uint64_t uncompressedSize = 0;
236 std::uint64_t nPages = 0;
237 std::uint32_t count = 0;
238
240 {
241 this->count++;
242 this->nElems += colInfo.GetNElements();
243 this->compressedSize += colInfo.GetCompressedSize();
244 this->uncompressedSize += colInfo.GetUncompressedSize();
245 this->nPages += colInfo.GetNPages();
246 }
247
248 // Helper method to calculate compression factor
249 float GetCompressionFactor() const
250 {
251 if (compressedSize == 0)
252 return 1.0;
253 return static_cast<float>(uncompressedSize) / static_cast<float>(compressedSize);
254 }
255 };
256
257 std::map<ENTupleColumnType, ColumnTypeInfo> colTypeInfo;
258
259 // Collect information for each column
260 for (const auto &[colId, colInfo] : fColumnInfo) {
261 colTypeInfo[colInfo.GetType()] += colInfo;
262 }
263
264 switch (format) {
266 output << " column type | count | # elements | compressed bytes | uncompressed bytes | compression ratio | "
267 "# pages \n"
268 << "----------------|---------|-------------|------------------|--------------------|-------------------|-"
269 "------\n";
270 for (const auto &[colType, typeInfo] : colTypeInfo)
271 output << std::setw(15) << RColumnElementBase::GetColumnTypeName(colType) << " |" << std::setw(8)
272 << typeInfo.count << " |" << std::setw(12) << typeInfo.nElems << " |" << std::setw(17)
273 << typeInfo.compressedSize << " |" << std::setw(19) << typeInfo.uncompressedSize << " |" << std::fixed
274 << std::setprecision(3) << std::setw(18) << typeInfo.GetCompressionFactor() << " |" << std::setw(6)
275 << typeInfo.nPages << " \n";
276 break;
278 output << "columnType,count,nElements,compressedSize,uncompressedSize,compressionFactor,nPages\n";
279 for (const auto &[colType, typeInfo] : colTypeInfo) {
280 output << RColumnElementBase::GetColumnTypeName(colType) << "," << typeInfo.count << "," << typeInfo.nElems
281 << "," << typeInfo.compressedSize << "," << typeInfo.uncompressedSize << "," << std::fixed
282 << std::setprecision(3) << typeInfo.GetCompressionFactor() << "," << typeInfo.nPages << '\n';
283 }
284 break;
285 default: R__ASSERT(false && "Invalid print format");
286 }
287}
288
289std::unique_ptr<TH1D>
291 std::string_view histName, std::string_view histTitle)
292{
293 if (histName.empty()) {
294 switch (histKind) {
295 case ENTupleInspectorHist::kCount: histName = "colTypeCountHist"; break;
296 case ENTupleInspectorHist::kNElems: histName = "colTypeElemCountHist"; break;
297 case ENTupleInspectorHist::kCompressedSize: histName = "colTypeCompSizeHist"; break;
298 case ENTupleInspectorHist::kUncompressedSize: histName = "colTypeUncompSizeHist"; break;
299 default: throw RException(R__FAIL("Unknown histogram type"));
300 }
301 }
302
303 if (histTitle.empty()) {
304 switch (histKind) {
305 case ENTupleInspectorHist::kCount: histTitle = "Column count by type"; break;
306 case ENTupleInspectorHist::kNElems: histTitle = "Number of elements by column type"; break;
307 case ENTupleInspectorHist::kCompressedSize: histTitle = "Compressed size by column type"; break;
308 case ENTupleInspectorHist::kUncompressedSize: histTitle = "Uncompressed size by column type"; break;
309 default: throw RException(R__FAIL("Unknown histogram type"));
310 }
311 }
312
313 auto hist = std::make_unique<TH1D>(std::string(histName).c_str(), std::string(histTitle).c_str(), 1, 0, 1);
314
315 double data;
316 for (const auto &[colId, colInfo] : fColumnInfo) {
317 switch (histKind) {
318 case ENTupleInspectorHist::kCount: data = 1.; break;
319 case ENTupleInspectorHist::kNElems: data = colInfo.GetNElements(); break;
320 case ENTupleInspectorHist::kCompressedSize: data = colInfo.GetCompressedSize(); break;
321 case ENTupleInspectorHist::kUncompressedSize: data = colInfo.GetUncompressedSize(); break;
322 default: throw RException(R__FAIL("Unknown histogram type"));
323 }
324
325 hist->AddBinContent(hist->GetXaxis()->FindBin(RColumnElementBase::GetColumnTypeName(colInfo.GetType())), data);
326 }
327
328 return hist;
329}
330
331std::unique_ptr<TH1D>
333 std::string histName, std::string histTitle, size_t nBins)
334{
335 if (histTitle.empty())
336 histTitle = "Page size distribution for column with ID " + std::to_string(physicalColumnId);
337
338 return GetPageSizeDistribution({physicalColumnId}, histName, histTitle, nBins);
339}
340
342 std::string histName,
343 std::string histTitle, size_t nBins)
344{
345 if (histName.empty())
346 histName = "pageSizeHistCol" + std::string{RColumnElementBase::GetColumnTypeName(colType)};
347 if (histTitle.empty())
348 histTitle =
349 "Page size distribution for columns with type " + std::string{RColumnElementBase::GetColumnTypeName(colType)};
350
351 auto perTypeHist = GetPageSizeDistribution({colType}, histName, histTitle, nBins);
352
353 if (perTypeHist->GetNhists() < 1)
354 return std::make_unique<TH1D>(histName.c_str(), histTitle.c_str(), 64, 0, 0);
355
356 auto hist = std::unique_ptr<TH1D>(dynamic_cast<TH1D *>(perTypeHist->GetHists()->First()));
357
358 hist->SetName(histName.c_str());
359 hist->SetTitle(histTitle.c_str());
360 hist->SetXTitle("Page size (B)");
361 hist->SetYTitle("N_{pages}");
362 return hist;
363}
364
365std::unique_ptr<TH1D>
367 std::string histName, std::string histTitle, size_t nBins)
368{
369 auto hist = std::make_unique<TH1D>();
370
371 if (histName.empty())
372 histName = "pageSizeHist";
373 hist->SetName(histName.c_str());
374 if (histTitle.empty())
375 histTitle = "Page size distribution";
376 hist->SetTitle(histTitle.c_str());
377 hist->SetXTitle("Page size (B)");
378 hist->SetYTitle("N_{pages}");
379
380 std::vector<std::uint64_t> pageSizes;
381 std::for_each(colIds.begin(), colIds.end(), [this, &pageSizes](const auto colId) {
382 auto colInfo = GetColumnInspector(colId);
383 pageSizes.insert(pageSizes.end(), colInfo.GetCompressedPageSizes().begin(),
384 colInfo.GetCompressedPageSizes().end());
385 });
386
387 if (!pageSizes.empty()) {
388 auto histMinMax = std::minmax_element(pageSizes.begin(), pageSizes.end());
389 hist->SetBins(nBins, *histMinMax.first,
390 *histMinMax.second + ((*histMinMax.second - *histMinMax.first) / static_cast<double>(nBins)));
391
392 for (const auto pageSize : pageSizes) {
393 hist->Fill(pageSize);
394 }
395 }
396
397 return hist;
398}
399
400std::unique_ptr<THStack>
401ROOT::Experimental::RNTupleInspector::GetPageSizeDistribution(std::initializer_list<ROOT::ENTupleColumnType> colTypes,
402 std::string histName, std::string histTitle, size_t nBins)
403{
404 if (histName.empty())
405 histName = "pageSizeHist";
406 if (histTitle.empty())
407 histTitle = "Per-column type page size distribution";
408
409 auto stackedHist = std::make_unique<THStack>(histName.c_str(), histTitle.c_str());
410
411 double histMin = std::numeric_limits<double>::max();
412 double histMax = 0;
413 std::map<ROOT::ENTupleColumnType, std::vector<std::uint64_t>> pageSizes;
414
415 std::vector<ROOT::ENTupleColumnType> colTypeVec = colTypes;
416 if (std::empty(colTypes)) {
417 colTypeVec = GetColumnTypes();
418 }
419
420 for (const auto colType : colTypeVec) {
421 auto colIds = GetColumnsByType(colType);
422
423 if (colIds.empty())
424 continue;
425
426 std::vector<std::uint64_t> pageSizesForColType;
427 std::for_each(colIds.cbegin(), colIds.cend(), [this, &pageSizesForColType](const auto colId) {
428 auto colInfo = GetColumnInspector(colId);
429 pageSizesForColType.insert(pageSizesForColType.end(), colInfo.GetCompressedPageSizes().begin(),
430 colInfo.GetCompressedPageSizes().end());
431 });
432 if (pageSizesForColType.empty())
433 continue;
434
436
437 auto histMinMax = std::minmax_element(pageSizesForColType.begin(), pageSizesForColType.end());
438 histMin = std::min(histMin, static_cast<double>(*histMinMax.first));
439 histMax = std::max(histMax, static_cast<double>(*histMinMax.second));
440 }
441
442 for (const auto &[colType, pageSizesForColType] : pageSizes) {
443 auto hist = std::make_unique<TH1D>(
446 histMax + ((histMax - histMin) / static_cast<double>(nBins)));
447
448 for (const auto pageSize : pageSizesForColType) {
449 hist->Fill(pageSize);
450 }
451
452 stackedHist->Add(hist.release());
453 }
454
455 return stackedHist;
456}
457
458//------------------------------------------------------------------------------
459
462{
463 if (fieldId >= fDescriptor.GetNFields()) {
464 throw RException(R__FAIL("No field with ID " + std::to_string(fieldId) + " present"));
465 }
466
467 return fFieldTreeInfo.at(fieldId);
468}
469
472{
473 auto fieldId = fDescriptor.FindFieldId(fieldName);
474
476 throw RException(R__FAIL("Could not find field `" + std::string(fieldName) + "`"));
477 }
478
479 return GetFieldTreeInspector(fieldId);
480}
481
483 bool includeSubfields) const
484{
485 size_t typeCount = 0;
486
487 for (auto &[fldId, fldInfo] : fFieldTreeInfo) {
488 if (!includeSubfields && fldInfo.GetDescriptor().GetParentId() != fDescriptor.GetFieldZeroId()) {
489 continue;
490 }
491
492 if (std::regex_match(fldInfo.GetDescriptor().GetTypeName(), typeNamePattern)) {
493 typeCount++;
494 }
495 }
496
497 return typeCount;
498}
499
500std::vector<ROOT::DescriptorId_t>
502{
503 std::vector<ROOT::DescriptorId_t> fieldIds;
504
505 for (auto &[fldId, fldInfo] : fFieldTreeInfo) {
506
507 if (!searchInSubfields && fldInfo.GetDescriptor().GetParentId() != fDescriptor.GetFieldZeroId()) {
508 continue;
509 }
510
511 if (std::regex_match(fldInfo.GetDescriptor().GetFieldName(), fieldNamePattern)) {
512 fieldIds.emplace_back(fldId);
513 }
514 }
515
516 return fieldIds;
517}
518
520 std::ostream &output) const
521{
522 const auto &tupleDescriptor = GetDescriptor();
523 const bool isZeroField = fieldDescriptor.GetParentId() == ROOT::kInvalidDescriptorId;
524 if (isZeroField) {
525 output << "digraph D {\n";
526 output << "node[shape=box]\n";
527 }
528 const std::string &nodeId = (isZeroField) ? "0" : std::to_string(fieldDescriptor.GetId() + 1);
529 const std::string &description = fieldDescriptor.GetFieldDescription();
530 const std::uint32_t &version = fieldDescriptor.GetFieldVersion();
531
532 auto htmlEscape = [&](const std::string &in) -> std::string {
533 std::string out;
534 out.reserve(in.size());
535 for (const char &c : in) {
536 switch (c) {
537 case '&': out += "&amp;"; break;
538 case '<': out += "&lt;"; break;
539 case '>': out += "&gt;"; break;
540 case '\"': out += "&quot;"; break;
541 case '\'': out += "&#39;"; break;
542 default: out += c; break;
543 }
544 }
545 return out;
546 };
547
548 output << nodeId << "[label=<";
549 if (!isZeroField) {
550 output << "<b>Name: </b>" << htmlEscape(fieldDescriptor.GetFieldName()) << "<br></br>";
551 output << "<b>Type: </b>" << htmlEscape(fieldDescriptor.GetTypeName()) << "<br></br>";
552 output << "<b>ID: </b>" << std::to_string(fieldDescriptor.GetId()) << "<br></br>";
553 if (description != "")
554 output << "<b>Description: </b>" << htmlEscape(description) << "<br></br>";
555 if (version != 0)
556 output << "<b>Version: </b>" << version << "<br></br>";
557 } else
558 output << "<b>RFieldZero</b>";
559 output << ">]\n";
560 for (const auto &childFieldId : fieldDescriptor.GetLinkIds()) {
561 const auto &childFieldDescriptor = tupleDescriptor.GetFieldDescriptor(childFieldId);
562 output << nodeId + "->" + std::to_string(childFieldDescriptor.GetId() + 1) + "\n";
563 PrintFieldTreeAsDot(childFieldDescriptor, output);
564 }
565 if (isZeroField)
566 output << "}";
567}
568
569namespace {
570
571struct SpeedscopeFrame {
572 std::string fString;
573 std::uint64_t fOpeningPosition = 0;
574 std::uint64_t fClosingPosition = 0;
575};
576
577static void PrintSpeedscopeFrames(const std::vector<SpeedscopeFrame> &frames, std::ostream &output)
578{
579 output << "{\n";
580 output << " \"$schema\":\"https://www.speedscope.app/file-format-schema.json\",\n";
581 output << " \"shared\":{\n";
582 output << " \"frames\":[\n";
583
584 for (std::size_t i = 0; i < frames.size(); ++i) {
585 output << " { \"name\":\"" << frames[i].fString << "\" }" << (i + 1 < frames.size() ? ",\n" : "\n");
586 }
587
588 output << " ]\n";
589 output << " },\n";
590 output << " \"profiles\":[\n";
591 output << " {\n";
592 output << " \"type\":\"evented\",\n";
593 output << " \"name\":\"Flattened Timeline\",\n";
594 output << " \"unit\":\"bytes\",\n";
595 output << " \"startValue\":0,\n";
596 output << " \"endValue\":" << frames.back().fClosingPosition << ",\n";
597 output << " \"events\":[\n";
598
599 bool first = true;
600
601 // Parameter idx Index of the frame being processed
602 // Parameter limit
603 // - If the frame is not root: Closing Position of its father
604 // - If the frame is root: Closing Position of the last element of frames
605 // Returns the next index to be processed
606 std::function<std::size_t(std::size_t, std::uint32_t)> processRecursive = [&](std::size_t nextIdxToProcess,
607 std::uint32_t limit) -> std::size_t {
608 while (nextIdxToProcess < frames.size() && frames[nextIdxToProcess].fOpeningPosition < limit) {
609 const std::size_t currentIdx = nextIdxToProcess;
610
611 if (!first)
612 output << ",\n";
613
614 output << " {\"type\":\"O\",\"frame\":" << currentIdx
615 << ",\"at\":" << frames[currentIdx].fOpeningPosition << "}";
616 first = false;
617
619
620 output << ",\n {\"type\":\"C\",\"frame\":" << currentIdx
621 << ",\"at\":" << frames[currentIdx].fClosingPosition << "}";
622 }
623 return nextIdxToProcess;
624 };
625
626 processRecursive(0, frames.back().fClosingPosition);
627
628 output << "\n ]\n";
629 output << " }\n";
630 output << " ]\n";
631 output << "}\n";
632}
633} // namespace
634
636 std::ostream &output) const
637{
638 // There is only one format at the moment
640
641 const auto &tupleDescriptor = GetDescriptor();
643 const auto &rootFieldDescriptor = tupleDescriptor.GetFieldDescriptor(rootId);
644
645 std::vector<SpeedscopeFrame> frames;
646 std::uint64_t positionCursor = 0;
647
648 // Returns size of the visited field
649 auto visitFieldsRecursive = [&](auto &self, const ROOT::RFieldDescriptor &fieldDescriptor) -> std::size_t {
650 SpeedscopeFrame fieldSpeedscopeFrame;
651 fieldSpeedscopeFrame.fString =
652 tupleDescriptor.GetQualifiedFieldName(fieldDescriptor.GetId()) + " (" + fieldDescriptor.GetTypeName() + ")";
653 fieldSpeedscopeFrame.fOpeningPosition = positionCursor;
654 frames.push_back(fieldSpeedscopeFrame);
655
656 std::size_t fieldSpeedscopeFrameIndex = frames.size() - 1;
657
658 std::size_t subTreeSize = 0;
659 const auto &childIds = fieldDescriptor.GetLinkIds();
660
661 for (const auto &childFieldId : childIds) {
662 const auto &childFieldDescriptor = tupleDescriptor.GetFieldDescriptor(childFieldId);
664 }
665
666 for (const auto &columnDescriptor : tupleDescriptor.GetColumnIterable(fieldDescriptor.GetId())) {
667 const auto &columnInfo = GetColumnInspector(columnDescriptor.GetPhysicalId());
668 std::size_t columnSize = columnInfo.GetCompressedSize();
669
670 SpeedscopeFrame columnSpeedscopeFrame;
671 columnSpeedscopeFrame.fString =
672 "[col#" + std::to_string(columnDescriptor.GetPhysicalId()) + "] " +
673 tupleDescriptor.GetQualifiedFieldName(fieldDescriptor.GetId()) + " (" +
675 columnSpeedscopeFrame.fOpeningPosition = positionCursor;
677 columnSpeedscopeFrame.fClosingPosition = positionCursor;
678 frames.push_back(columnSpeedscopeFrame);
680 }
681
683
684 return subTreeSize;
685 };
686
687 const auto &topLevelIds = rootFieldDescriptor.GetLinkIds();
688 for (const auto &childId : topLevelIds) {
689 const auto &childFieldDescriptor = tupleDescriptor.GetFieldDescriptor(childId);
691 }
692
694}
695
697 std::ostream &output) const
698{
699 // There is only one format at the moment
701
702 const auto *pageSourceFile = dynamic_cast<const ROOT::Internal::RPageSourceFile *>(fPageSource.get());
703 // GetAnchorFromFile() only supports file-based backend, so better to check early
704 if (!pageSourceFile)
705 throw RException(R__FAIL("Disk profile is only supported for file-based page sources"));
707 if (!anchor)
708 R__LOG_WARNING(ROOT::Internal::NTupleLog()) << "Cannot retrieve RNTuple anchor";
709
710 const auto &descriptor = GetDescriptor();
711
712 struct RDiskPageLeaf {
713 std::uint64_t fPosition = 0;
714 std::uint64_t fSize = 0;
715 std::string fName;
716 std::array<DescriptorId_t, 3> fAncestors;
717 };
718 static constexpr std::array<const char *, 3> kAncestorsNames = {"cluster group", "cluster", "column range"};
719 std::vector<RDiskPageLeaf> pageLeaves;
720
721 // Collect all pageLeaves in whichever order the iterator provides
722 for (const auto &clusterGroupDescriptor : descriptor.GetClusterGroupIterable()) {
723 const auto groupId = clusterGroupDescriptor.GetId();
724
725 for (const auto clusterId : clusterGroupDescriptor.GetClusterIds()) {
726 const auto &clusterDescriptor = descriptor.GetClusterDescriptor(clusterId);
727
728 for (const auto &columnRange : clusterDescriptor.GetColumnRangeIterable()) {
729 const auto columnId = columnRange.GetPhysicalColumnId();
730
731 const auto &pageRange = clusterDescriptor.GetPageRange(columnId);
732 for (const auto &pageInfo : pageRange.GetPageInfos()) {
733 const auto &locator = pageInfo.GetLocator();
734
736 pageLeaf.fPosition = locator.GetPosition<std::uint64_t>();
737 pageLeaf.fSize = locator.GetNBytesOnStorage();
738 pageLeaf.fName = "[page @" + std::to_string(pageLeaf.fPosition) + "]";
739 pageLeaf.fAncestors = {groupId, clusterId, columnId};
740 pageLeaves.push_back(pageLeaf);
741 }
742 }
743 }
744 }
745
746 // Sort pageLeafs by on-disk address
747 std::sort(pageLeaves.begin(), pageLeaves.end(),
748 [](const RDiskPageLeaf &a, const RDiskPageLeaf &b) { return a.fPosition < b.fPosition; });
749
750 // Remove aliases (the ntuple specification allows complete, but not partial, overlap between pages)
751 pageLeaves.erase(
752 std::unique(pageLeaves.begin(), pageLeaves.end(),
753 [](const RDiskPageLeaf &a, const RDiskPageLeaf &b) { return a.fPosition == b.fPosition; }),
754 pageLeaves.end());
755
756 std::vector<SpeedscopeFrame> frames;
757
758 // Construct frame for ntuple header
759 if (anchor) {
760 SpeedscopeFrame headerFrame;
761 headerFrame.fString = "ntuple header";
762 headerFrame.fOpeningPosition = anchor->GetSeekHeader();
763 headerFrame.fClosingPosition = anchor->GetSeekHeader() + anchor->GetNBytesHeader();
764 frames.push_back(headerFrame);
765 }
766
767 struct ROpenFrame {
768 ROOT::DescriptorId_t fId = 0; // clusterGroup, cluster, columnRange id
769 std::size_t fIndex = 0; // index in frames vector
770 };
771 std::vector<ROpenFrame> openFrames;
772 std::uint64_t previouspageLeafEnd = 0;
773
774 // Construct frames from the bottom (leafs ordered by disk address) upwards
775 for (const auto &pageLeaf : pageLeaves) {
776 std::size_t sharedDepth = 0;
777
778 // How many of the currently open ancestors does this pageLeaf share?
779 while (sharedDepth < openFrames.size() && sharedDepth < pageLeaf.fAncestors.size() &&
780 openFrames[sharedDepth].fId == pageLeaf.fAncestors[sharedDepth]) {
781 sharedDepth++;
782 }
783
784 // Close ancestors not shared with this pageLeaf (innermost first order)
785 while (openFrames.size() > sharedDepth) {
786 frames[openFrames.back().fIndex].fClosingPosition = previouspageLeafEnd;
787 openFrames.pop_back();
788 }
789
790 // Open the ancestors this pageLeaf needs (outermost first order)
791 for (std::size_t depth = sharedDepth; depth < pageLeaf.fAncestors.size(); ++depth) {
792 SpeedscopeFrame ancestorFrame;
793 ancestorFrame.fString =
794 "[" + std::string(kAncestorsNames[depth]) + " " + std::to_string(pageLeaf.fAncestors[depth]) + "]";
795 ancestorFrame.fOpeningPosition = pageLeaf.fPosition;
796 frames.push_back(ancestorFrame);
797
799 openFrame.fId = pageLeaf.fAncestors[depth];
800 openFrame.fIndex = frames.size() - 1;
801 openFrames.push_back(openFrame);
802 }
803
804 // Emit the pageLeaf itself
805 SpeedscopeFrame pageLeafFrame;
806 pageLeafFrame.fString = pageLeaf.fName;
807 pageLeafFrame.fOpeningPosition = pageLeaf.fPosition;
808 pageLeafFrame.fClosingPosition = pageLeaf.fPosition + pageLeaf.fSize;
809 frames.push_back(pageLeafFrame);
810
811 previouspageLeafEnd = pageLeaf.fPosition + pageLeaf.fSize;
812 }
813
814 // Close whatever is still open after the last pageLeaf
815 while (!openFrames.empty()) {
816 frames[openFrames.back().fIndex].fClosingPosition = previouspageLeafEnd;
817 openFrames.pop_back();
818 }
819
820 // Construct frames for page lists
821 for (const auto &clusterGroupDescriptor : descriptor.GetClusterGroupIterable()) {
822 const auto locator = clusterGroupDescriptor.GetPageListLocator();
823
824 SpeedscopeFrame pageListFrame;
825 pageListFrame.fString = "[page list " + std::to_string(clusterGroupDescriptor.GetId()) + "]";
826 pageListFrame.fOpeningPosition = locator.GetPosition<std::uint64_t>();
827 pageListFrame.fClosingPosition = locator.GetPosition<std::uint64_t>() + locator.GetNBytesOnStorage();
828 frames.push_back(pageListFrame);
829 }
830
831 // Construct frame for ntuple footer
832 if (anchor) {
833 SpeedscopeFrame footerFrame;
834 footerFrame.fString = "ntuple footer";
835 footerFrame.fOpeningPosition = anchor->GetSeekFooter();
836 footerFrame.fClosingPosition = anchor->GetSeekFooter() + anchor->GetNBytesFooter();
837 frames.push_back(footerFrame);
838 }
839
841}
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
ROOT::Detail::TRangeCast< T, true > TRangeDynCast
TRangeDynCast is an adapter class that allows the typed iteration through a TCollection.
#define R__ASSERT(e)
Checks condition e and reports a fatal error if it's false.
Definition TError.h:125
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:497
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.
void PrintSchemaProfile(ESchemaProfileFormat format, std::ostream &output=std::cout) const
Print a string that represents the tree of the (sub)fields and columns of an RNTuple in a format whic...
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.
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.
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 PrintDiskProfile(ESchemaProfileFormat format, std::ostream &output=std::cout) const
Print a string that represents the on-disk storage of the cluster groups, clusters,...
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)
Base class for all ROOT issued exceptions.
Definition RError.hxx:78
Metadata stored for every field of an RNTuple.
ROOT::DescriptorId_t GetFieldZeroId() const
Returns the logical parent of all top-level RNTuple data fields.
Representation of an RNTuple data set in a ROOT file.
Definition RNTuple.hxx:67
const_iterator begin() const
const_iterator end() const
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:2459
@ 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)