Skip to content

Commit e348748

Browse files
committed
[ntuple] Add minimal disk profile visualizer backend
Shows cluster groups, clusters, column ranges and pages. Comes with a test which stress tests the method with an ntuple shuffled on the 3 uppermost layers of the hierarchy: cluster groups, clusters, column ranges (pages are by definition uninterrupted serialized blobs).
1 parent da5fba1 commit e348748

3 files changed

Lines changed: 424 additions & 0 deletions

File tree

tree/ntupleutil/inc/ROOT/RNTupleInspector.hxx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,13 @@ public:
503503
/// \brief Print a string that represents the tree of the (sub)fields and columns of an RNTuple in a format which a
504504
/// performance profile visualizer can render
505505
void PrintSchemaProfile(ESchemaProfileFormat format, std::ostream &output = std::cout) const;
506+
507+
////////////////////////////////////////////////////////////////////////////
508+
/// \brief Print a string that represents the on-disk storage of the cluster groups, clusters, column ranges and
509+
/// pages on an RNTuple in a format which a performance profile visualizer can render
510+
///
511+
/// \warning ntuple must have a file-based backend
512+
void PrintDiskProfile(ESchemaProfileFormat format, std::ostream &output = std::cout) const;
506513
};
507514
} // namespace Experimental
508515
} // namespace ROOT

tree/ntupleutil/src/RNTupleInspector.cxx

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -691,3 +691,114 @@ void ROOT::Experimental::RNTupleInspector::PrintSchemaProfile([[maybe_unused]] E
691691

692692
PrintSpeedscopeFrames(frames, output);
693693
}
694+
695+
void ROOT::Experimental::RNTupleInspector::PrintDiskProfile([[maybe_unused]] ESchemaProfileFormat format,
696+
std::ostream &output) const
697+
{
698+
// There is only one format at the moment
699+
assert(format == ESchemaProfileFormat::kSpeedscopeJSON);
700+
701+
const auto &descriptor = GetDescriptor();
702+
703+
struct RDiskPageLeaf {
704+
std::uint64_t fPosition = 0;
705+
std::uint64_t fSize = 0;
706+
std::string fName;
707+
std::array<DescriptorId_t, 3> fAncestors;
708+
};
709+
static constexpr std::array<const char *, 3> kAncestorsNames = {"cluster group", "cluster", "column range"};
710+
std::vector<RDiskPageLeaf> pageLeaves;
711+
712+
// Collect all pageLeaves in whichever order the iterator provides
713+
for (const auto &clusterGroupDescriptor : descriptor.GetClusterGroupIterable()) {
714+
const auto groupId = clusterGroupDescriptor.GetId();
715+
716+
for (const auto clusterId : clusterGroupDescriptor.GetClusterIds()) {
717+
const auto &clusterDescriptor = descriptor.GetClusterDescriptor(clusterId);
718+
719+
for (const auto &columnRange : clusterDescriptor.GetColumnRangeIterable()) {
720+
const auto columnId = columnRange.GetPhysicalColumnId();
721+
722+
const auto &pageRange = clusterDescriptor.GetPageRange(columnId);
723+
for (const auto &pageInfo : pageRange.GetPageInfos()) {
724+
const auto &locator = pageInfo.GetLocator();
725+
726+
RDiskPageLeaf pageLeaf;
727+
pageLeaf.fPosition = locator.GetPosition<std::uint64_t>();
728+
pageLeaf.fSize = locator.GetNBytesOnStorage();
729+
pageLeaf.fName = "[page @" + std::to_string(pageLeaf.fPosition) + "]";
730+
pageLeaf.fAncestors = {groupId, clusterId, columnId};
731+
pageLeaves.push_back(pageLeaf);
732+
}
733+
}
734+
}
735+
}
736+
737+
// Sort pageLeafs by on-disk address
738+
std::sort(pageLeaves.begin(), pageLeaves.end(),
739+
[](const RDiskPageLeaf &a, const RDiskPageLeaf &b) { return a.fPosition < b.fPosition; });
740+
741+
// Remove aliases (the ntuple specification allows complete, but not partial, overlap between pages)
742+
pageLeaves.erase(
743+
std::unique(pageLeaves.begin(), pageLeaves.end(),
744+
[](const RDiskPageLeaf &a, const RDiskPageLeaf &b) { return a.fPosition == b.fPosition; }),
745+
pageLeaves.end());
746+
747+
std::vector<SpeedscopeFrame> frames;
748+
749+
struct ROpenFrame {
750+
ROOT::DescriptorId_t fId = 0; // clusterGroup, cluster, columnRange id
751+
std::size_t fIndex = 0; // index in frames vector
752+
};
753+
std::vector<ROpenFrame> openFrames;
754+
755+
std::uint64_t previouspageLeafEnd = 0;
756+
757+
// Construct frames from the bottom (leafs ordered by disk address) upwards
758+
for (const auto &pageLeaf : pageLeaves) {
759+
std::size_t sharedDepth = 0;
760+
761+
// How many of the currently open ancestors does this pageLeaf share?
762+
while (sharedDepth < openFrames.size() && sharedDepth < pageLeaf.fAncestors.size() &&
763+
openFrames[sharedDepth].fId == pageLeaf.fAncestors[sharedDepth]) {
764+
sharedDepth++;
765+
}
766+
767+
// Close ancestors not shared with this pageLeaf (innermost first order)
768+
while (openFrames.size() > sharedDepth) {
769+
frames[openFrames.back().fIndex].fClosingPosition = previouspageLeafEnd;
770+
openFrames.pop_back();
771+
}
772+
773+
// Open the ancestors this pageLeaf needs (outermost first order)
774+
for (std::size_t depth = sharedDepth; depth < pageLeaf.fAncestors.size(); ++depth) {
775+
SpeedscopeFrame ancestorFrame;
776+
ancestorFrame.fString =
777+
"[" + std::string(kAncestorsNames[depth]) + " " + std::to_string(pageLeaf.fAncestors[depth]) + "]";
778+
ancestorFrame.fOpeningPosition = pageLeaf.fPosition;
779+
frames.push_back(ancestorFrame);
780+
781+
ROpenFrame openFrame;
782+
openFrame.fId = pageLeaf.fAncestors[depth];
783+
openFrame.fIndex = frames.size() - 1;
784+
openFrames.push_back(openFrame);
785+
}
786+
787+
// Emit the pageLeaf itself
788+
SpeedscopeFrame pageLeafFrame;
789+
pageLeafFrame.fString = pageLeaf.fName;
790+
pageLeafFrame.fOpeningPosition = pageLeaf.fPosition;
791+
pageLeafFrame.fClosingPosition = pageLeaf.fPosition + pageLeaf.fSize;
792+
frames.push_back(pageLeafFrame);
793+
794+
previouspageLeafEnd = pageLeaf.fPosition + pageLeaf.fSize;
795+
}
796+
797+
// Close whatever is still open after the last pageLeaf
798+
while (!openFrames.empty()) {
799+
frames[openFrames.back().fIndex].fClosingPosition = previouspageLeafEnd;
800+
openFrames.pop_back();
801+
}
802+
803+
PrintSpeedscopeFrames(frames, output);
804+
}

0 commit comments

Comments
 (0)