Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions tree/ntuple/inc/ROOT/RPageStorageFile.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ namespace Internal {
class RRawFile;
class RPageAllocatorHeap;

RNTuple GetAnchor(const RPageSource &source);

// clang-format off
/**
\class ROOT::Internal::RPageSinkFile
Expand Down Expand Up @@ -122,6 +124,7 @@ public:
// clang-format on
class RPageSourceFile : public RPageSource {
friend class ROOT::RNTuple;
friend ROOT::RNTuple ROOT::Internal::GetAnchor(const RPageSource &);

private:
/// Either provided by CreateFromAnchor, or read from the ROOT file given the ntuple name
Expand Down
10 changes: 10 additions & 0 deletions tree/ntuple/src/RPageStorageFile.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,16 @@ ROOT::Internal::RPageSinkFile::CloneAsHidden(std::string_view name, const ROOT::

////////////////////////////////////////////////////////////////////////////////

ROOT::RNTuple ROOT::Internal::GetAnchor(const RPageSource &source)
{
const auto *fileSource = dynamic_cast<const RPageSourceFile *>(&source);
if (!fileSource)
throw RException(R__FAIL("Cannot retrieve an RNTuple anchor from a non-file page source"));
if (!fileSource->fAnchor)
throw RException(R__FAIL("Cannot retrieve RNTuple anchor: no anchor is available"));
return *fileSource->fAnchor;
}

ROOT::Internal::RPageSourceFile::RPageSourceFile(std::string_view ntupleName, const ROOT::RNTupleReadOptions &opts)
: RPageSource(ntupleName, opts)
{
Expand Down
7 changes: 7 additions & 0 deletions tree/ntupleutil/inc/ROOT/RNTupleInspector.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,13 @@ public:
/// \brief Print a string that represents the tree of the (sub)fields and columns of an RNTuple in a format which a
/// performance profile visualizer can render
void PrintSchemaProfile(ESchemaProfileFormat format, std::ostream &output = std::cout) const;

////////////////////////////////////////////////////////////////////////////
/// \brief Print a string that represents the on-disk storage of the cluster groups, clusters, column ranges, pages,
/// header, footer and page lists on an RNTuple in a format which a performance profile visualizer can render
///
/// \warning ntuple must have a file-based backend
void PrintDiskProfile(ESchemaProfileFormat format, std::ostream &output = std::cout) const;
};
} // namespace Experimental
} // namespace ROOT
Expand Down
136 changes: 136 additions & 0 deletions tree/ntupleutil/src/RNTupleInspector.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -691,3 +691,139 @@ void ROOT::Experimental::RNTupleInspector::PrintSchemaProfile([[maybe_unused]] E

PrintSpeedscopeFrames(frames, output);
}

void ROOT::Experimental::RNTupleInspector::PrintDiskProfile([[maybe_unused]] ESchemaProfileFormat format,
std::ostream &output) const
{
// There is only one format at the moment
assert(format == ESchemaProfileFormat::kSpeedscopeJSON);

// This method only supports file based backend. Will need the anchor later, but better to check early
const auto anchor = ROOT::Internal::GetAnchor(*fPageSource);

const auto &descriptor = GetDescriptor();

struct RDiskPageLeaf {
std::uint64_t fPosition = 0;
std::uint64_t fSize = 0;
std::string fName;
std::array<ROOT::DescriptorId_t, 3> fAncestorIds; // clusterGroup, cluster, columnRange
std::array<std::string, 3> fAncestorNames; // clusterGroup, cluster, columnRange
};
std::vector<RDiskPageLeaf> pageLeaves;

// Collect all pageLeaves in whichever order the iterator provides
for (const auto &clusterGroupDescriptor : descriptor.GetClusterGroupIterable()) {
const auto groupId = clusterGroupDescriptor.GetId();
const std::string groupName = "[cluster group " + std::to_string(groupId) + "]";

for (const auto clusterId : clusterGroupDescriptor.GetClusterIds()) {
const auto &clusterDescriptor = descriptor.GetClusterDescriptor(clusterId);
const std::string clusterName = "[cluster " + std::to_string(clusterId) + "]";

for (const auto &columnRange : clusterDescriptor.GetColumnRangeIterable()) {
const auto columnId = columnRange.GetPhysicalColumnId();
const std::string columnRangeName = "[column range " + std::to_string(columnId) + "]";

const auto &pageRange = clusterDescriptor.GetPageRange(columnId);
for (const auto &pageInfo : pageRange.GetPageInfos()) {
const auto &locator = pageInfo.GetLocator();

RDiskPageLeaf pageLeaf;
pageLeaf.fPosition = locator.GetPosition<std::uint64_t>();
pageLeaf.fSize = locator.GetNBytesOnStorage();
pageLeaf.fName = "[page @" + std::to_string(pageLeaf.fPosition) + "]";
pageLeaf.fAncestorIds = {groupId + 1, clusterId + 1, columnId + 1};
pageLeaf.fAncestorNames = {groupName, clusterName, columnRangeName};
pageLeaves.push_back(pageLeaf);
}
}
}
}

// Sort pageLeafs by on-disk address
std::sort(pageLeaves.begin(), pageLeaves.end(),
[](const RDiskPageLeaf &a, const RDiskPageLeaf &b) { return a.fPosition < b.fPosition; });

// Remove aliases (the ntuple specification allows complete, but not partial, overlap between pages)
pageLeaves.erase(
std::unique(pageLeaves.begin(), pageLeaves.end(),
[](const RDiskPageLeaf &a, const RDiskPageLeaf &b) { return a.fPosition == b.fPosition; }),
pageLeaves.end());

std::vector<SpeedscopeFrame> frames;
std::vector<ROOT::DescriptorId_t> openIds;
std::vector<std::size_t> openFrameIndexes;
std::uint64_t previouspageLeafEnd = 0;

// Construct frame for ntuple header
SpeedscopeFrame headerFrame;
headerFrame.fString = "ntuple header";
headerFrame.fOpeningPosition = anchor.GetSeekHeader();
headerFrame.fClosingPosition = anchor.GetSeekHeader() + anchor.GetNBytesHeader();
frames.push_back(headerFrame);

// Construct frames from the bottom (leafs ordered by disk address) upwards
for (const auto &pageLeaf : pageLeaves) {
std::size_t sharedDepth = 0;

// How many of the currently open ancestors does this pageLeaf share?
while (sharedDepth < openIds.size() && sharedDepth < pageLeaf.fAncestorIds.size() &&
openIds[sharedDepth] == pageLeaf.fAncestorIds[sharedDepth]) {
sharedDepth++;
}

// Close ancestors not shared with this pageLeaf (innermost first order)
while (openIds.size() > sharedDepth) {
frames[openFrameIndexes.back()].fClosingPosition = previouspageLeafEnd;
openIds.pop_back();
openFrameIndexes.pop_back();
}

// Open the ancestors this pageLeaf needs (outermost first order)
for (std::size_t depth = sharedDepth; depth < pageLeaf.fAncestorIds.size(); ++depth) {
SpeedscopeFrame ancestorFrame;
ancestorFrame.fString = pageLeaf.fAncestorNames[depth];
ancestorFrame.fOpeningPosition = pageLeaf.fPosition;
frames.push_back(ancestorFrame);
openIds.push_back(pageLeaf.fAncestorIds[depth]);
openFrameIndexes.push_back(frames.size() - 1);
}

// Emit the pageLeaf itself
SpeedscopeFrame pageLeafFrame;
pageLeafFrame.fString = pageLeaf.fName;
pageLeafFrame.fOpeningPosition = pageLeaf.fPosition;
pageLeafFrame.fClosingPosition = pageLeaf.fPosition + pageLeaf.fSize;
frames.push_back(pageLeafFrame);

previouspageLeafEnd = pageLeaf.fPosition + pageLeaf.fSize;
}

// Close whatever is still open after the last pageLeaf
while (!openIds.empty()) {
frames[openFrameIndexes.back()].fClosingPosition = previouspageLeafEnd;
openIds.pop_back();
openFrameIndexes.pop_back();
}

// Construct frames for page lists
for (const auto &clusterGroupDescriptor : descriptor.GetClusterGroupIterable()) {
const auto locator = clusterGroupDescriptor.GetPageListLocator();

SpeedscopeFrame pageListFrame;
pageListFrame.fString = "[page list " + std::to_string(clusterGroupDescriptor.GetId()) + "]";
pageListFrame.fOpeningPosition = locator.GetPosition<std::uint64_t>();
pageListFrame.fClosingPosition = locator.GetPosition<std::uint64_t>() + locator.GetNBytesOnStorage();
frames.push_back(pageListFrame);
}

// Construct frame for ntuple footer
SpeedscopeFrame footerFrame;
footerFrame.fString = "ntuple footer";
footerFrame.fOpeningPosition = anchor.GetSeekFooter();
footerFrame.fClosingPosition = anchor.GetSeekFooter() + anchor.GetNBytesFooter();
frames.push_back(footerFrame);

PrintSpeedscopeFrames(frames, output);
}
Loading