Skip to content

Commit bd5cfd3

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 bd5cfd3

3 files changed

Lines changed: 417 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: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -691,3 +691,111 @@ 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<ROOT::DescriptorId_t, 3> fAncestorIds; // clusterGroup, cluster, columnRange
708+
std::array<std::string, 3> fAncestorNames; // clusterGroup, cluster, columnRange
709+
};
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+
const std::string groupName = "[cluster group " + std::to_string(groupId) + "]";
716+
717+
for (const auto clusterId : clusterGroupDescriptor.GetClusterIds()) {
718+
const auto &clusterDescriptor = descriptor.GetClusterDescriptor(clusterId);
719+
const std::string clusterName = "[cluster " + std::to_string(clusterId) + "]";
720+
721+
for (const auto &columnRange : clusterDescriptor.GetColumnRangeIterable()) {
722+
const auto columnId = columnRange.GetPhysicalColumnId();
723+
const std::string columnRangeName = "[column range " + std::to_string(columnId) + "]";
724+
725+
const auto &pageRange = clusterDescriptor.GetPageRange(columnId);
726+
for (const auto &pageInfo : pageRange.GetPageInfos()) {
727+
const auto &locator = pageInfo.GetLocator();
728+
729+
RDiskPageLeaf pageLeaf;
730+
pageLeaf.fPosition = locator.GetPosition<std::uint64_t>();
731+
pageLeaf.fSize = locator.GetNBytesOnStorage();
732+
pageLeaf.fName = "[page @" + std::to_string(pageLeaf.fPosition) + "]";
733+
pageLeaf.fAncestorIds = {groupId + 1, clusterId + 1, columnId + 1};
734+
pageLeaf.fAncestorNames = {groupName, clusterName, columnRangeName};
735+
pageLeaves.push_back(pageLeaf);
736+
}
737+
}
738+
}
739+
}
740+
741+
// Sort pageLeafs by on-disk address
742+
std::sort(pageLeaves.begin(), pageLeaves.end(),
743+
[](const RDiskPageLeaf &a, const RDiskPageLeaf &b) { return a.fPosition < b.fPosition; });
744+
745+
// Remove aliases (the ntuple specification allows complete, but not partial, overlap between pages)
746+
pageLeaves.erase(
747+
std::unique(pageLeaves.begin(), pageLeaves.end(),
748+
[](const RDiskPageLeaf &a, const RDiskPageLeaf &b) { return a.fPosition == b.fPosition; }),
749+
pageLeaves.end());
750+
751+
std::vector<SpeedscopeFrame> frames;
752+
std::vector<ROOT::DescriptorId_t> openIds;
753+
std::vector<std::size_t> openFrameIndexes;
754+
std::uint64_t previouspageLeafEnd = 0;
755+
756+
// Construct frames from the bottom (leafs ordered by disk address) upwards
757+
for (const auto &pageLeaf : pageLeaves) {
758+
std::size_t sharedDepth = 0;
759+
760+
// How many of the currently open ancestors does this pageLeaf share?
761+
while (sharedDepth < openIds.size() && sharedDepth < pageLeaf.fAncestorIds.size() &&
762+
openIds[sharedDepth] == pageLeaf.fAncestorIds[sharedDepth]) {
763+
sharedDepth++;
764+
}
765+
766+
// Close ancestors not shared with this pageLeaf (innermost first order)
767+
while (openIds.size() > sharedDepth) {
768+
frames[openFrameIndexes.back()].fClosingPosition = previouspageLeafEnd;
769+
openIds.pop_back();
770+
openFrameIndexes.pop_back();
771+
}
772+
773+
// Open the ancestors this pageLeaf needs (outermost first order)
774+
for (std::size_t depth = sharedDepth; depth < pageLeaf.fAncestorIds.size(); ++depth) {
775+
SpeedscopeFrame ancestorFrame;
776+
ancestorFrame.fString = pageLeaf.fAncestorNames[depth];
777+
ancestorFrame.fOpeningPosition = pageLeaf.fPosition;
778+
frames.push_back(ancestorFrame);
779+
openIds.push_back(pageLeaf.fAncestorIds[depth]);
780+
openFrameIndexes.push_back(frames.size() - 1);
781+
}
782+
783+
// Emit the pageLeaf itself
784+
SpeedscopeFrame pageLeafFrame;
785+
pageLeafFrame.fString = pageLeaf.fName;
786+
pageLeafFrame.fOpeningPosition = pageLeaf.fPosition;
787+
pageLeafFrame.fClosingPosition = pageLeaf.fPosition + pageLeaf.fSize;
788+
frames.push_back(pageLeafFrame);
789+
790+
previouspageLeafEnd = pageLeaf.fPosition + pageLeaf.fSize;
791+
}
792+
793+
// Close whatever is still open after the last pageLeaf
794+
while (!openIds.empty()) {
795+
frames[openFrameIndexes.back()].fClosingPosition = previouspageLeafEnd;
796+
openIds.pop_back();
797+
openFrameIndexes.pop_back();
798+
}
799+
800+
PrintSpeedscopeFrames(frames, output);
801+
}

0 commit comments

Comments
 (0)