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 GetAnchorFromFile(const RPageSourceFile &source);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
RNTuple GetAnchorFromFile(const RPageSourceFile &source);
const ROOT::RNTuple &GetAnchorFromFile(const RPageSourceFile &source);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Or, better, you could return const ROOT::RNTuple * and avoid throwing if the anchor is not there (leave the decision to the caller)


// 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::GetAnchorFromFile(const RPageSourceFile &);

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

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

ROOT::RNTuple ROOT::Internal::GetAnchorFromFile(const RPageSourceFile &source)
{
if (!source.fAnchor)
throw RException(R__FAIL("Cannot retrieve RNTuple anchor: no anchor is available"));
return *source.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
142 changes: 142 additions & 0 deletions tree/ntupleutil/src/RNTupleInspector.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -691,3 +691,145 @@ 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);

// GetAnchorFromFile() only supports file based backend. Will need the anchor later, but better to check early
const auto *pageSourceFile = dynamic_cast<const ROOT::Internal::RPageSourceFile *>(fPageSource.get());
if (!pageSourceFile)
throw RException(R__FAIL("disk profile is only supported for file-based page sources"));
const auto anchor = ROOT::Internal::GetAnchorFromFile(*pageSourceFile);

const auto &descriptor = GetDescriptor();

struct RDiskPageLeaf {
std::uint64_t fPosition = 0;
std::uint64_t fSize = 0;
std::string fName;
std::array<DescriptorId_t, 3> fAncestors;
};
static constexpr std::array<const char *, 3> kAncestorsNames = {"cluster group", "cluster", "column range"};
std::vector<RDiskPageLeaf> pageLeaves;

// Collect all pageLeaves in whichever order the iterator provides
for (const auto &clusterGroupDescriptor : descriptor.GetClusterGroupIterable()) {
const auto groupId = clusterGroupDescriptor.GetId();

for (const auto clusterId : clusterGroupDescriptor.GetClusterIds()) {
const auto &clusterDescriptor = descriptor.GetClusterDescriptor(clusterId);

for (const auto &columnRange : clusterDescriptor.GetColumnRangeIterable()) {
const auto columnId = columnRange.GetPhysicalColumnId();

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.fAncestors = {groupId, clusterId, columnId};
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;

struct ROpenFrame {
ROOT::DescriptorId_t fId = 0; // clusterGroup, cluster, columnRange id
std::size_t fIndex = 0; // index in frames vector
};
std::vector<ROpenFrame> openFrames;

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 < openFrames.size() && sharedDepth < pageLeaf.fAncestors.size() &&
openFrames[sharedDepth].fId == pageLeaf.fAncestors[sharedDepth]) {
sharedDepth++;
}

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

// Open the ancestors this pageLeaf needs (outermost first order)
for (std::size_t depth = sharedDepth; depth < pageLeaf.fAncestors.size(); ++depth) {
SpeedscopeFrame ancestorFrame;
ancestorFrame.fString =
"[" + std::string(kAncestorsNames[depth]) + " " + std::to_string(pageLeaf.fAncestors[depth]) + "]";
ancestorFrame.fOpeningPosition = pageLeaf.fPosition;
frames.push_back(ancestorFrame);

ROpenFrame openFrame;
openFrame.fId = pageLeaf.fAncestors[depth];
openFrame.fIndex = frames.size() - 1;
openFrames.push_back(openFrame);
}

// 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 (!openFrames.empty()) {
frames[openFrames.back().fIndex].fClosingPosition = previouspageLeafEnd;
openFrames.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