@@ -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