Skip to content

[ntuple] Fields tree as dot #19319

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 15, 2025
Merged
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
36 changes: 31 additions & 5 deletions tree/ntupleutil/v7/inc/ROOT/RNTupleInspector.hxx
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,16 @@ class RPageSource;

namespace Experimental {

enum class ENTupleInspectorPrintFormat { kTable, kCSV };
enum class ENTupleInspectorHist { kCount, kNElems, kCompressedSize, kUncompressedSize };
enum class ENTupleInspectorPrintFormat {
kTable,
kCSV
};
enum class ENTupleInspectorHist {
kCount,
kNElems,
kCompressedSize,
kUncompressedSize
};

// clang-format off
/**
Expand Down Expand Up @@ -93,15 +101,16 @@ public:
: fColumnDescriptor(colDesc),
fCompressedPageSizes(compressedPageSizes),
fElementSize(elemSize),
fNElements(nElems) {};
fNElements(nElems){};
~RColumnInspector() = default;

const ROOT::RColumnDescriptor &GetDescriptor() const { return fColumnDescriptor; }
const std::vector<std::uint64_t> &GetCompressedPageSizes() const { return fCompressedPageSizes; }
std::uint64_t GetNPages() const { return fCompressedPageSizes.size(); }
std::uint64_t GetCompressedSize() const
{
return std::accumulate(fCompressedPageSizes.begin(), fCompressedPageSizes.end(), static_cast<std::uint64_t>(0));
return std::accumulate(fCompressedPageSizes.begin(), fCompressedPageSizes.end(),
static_cast<std::uint64_t>(0));
}
std::uint64_t GetUncompressedSize() const { return fElementSize * fNElements; }
std::uint64_t GetElementSize() const { return fElementSize; }
Expand All @@ -123,7 +132,7 @@ public:

public:
RFieldTreeInspector(const ROOT::RFieldDescriptor &fieldDesc, std::uint64_t onDiskSize, std::uint64_t inMemSize)
: fRootFieldDescriptor(fieldDesc), fCompressedSize(onDiskSize), fUncompressedSize(inMemSize) {};
: fRootFieldDescriptor(fieldDesc), fCompressedSize(onDiskSize), fUncompressedSize(inMemSize){};
~RFieldTreeInspector() = default;

const ROOT::RFieldDescriptor &GetDescriptor() const { return fRootFieldDescriptor; }
Expand Down Expand Up @@ -469,6 +478,23 @@ public:
{
return GetFieldsByName(std::regex{std::string(fieldNamePattern)}, searchInSubfields);
}
/////////////////////////////////////////////////////////////////////////////
/// \brief Print a .dot string that represents the tree of the (sub)fields of an RNTuple
///
/// \param[in] fieldDescriptor The descriptor of the root field (this method works recursively)
///

void PrintFieldTreeAsDot(const ROOT::RFieldDescriptor &fieldDescriptor, std::ostream &output = std::cout) const;

/////////////////////////////////////////////////////////////////////////////
/// \brief Print the tree of all the (sub)fields of an RNTuple
/// \param[in] output
///
/// \see PrintFieldTreeAsDot(const ROOT::RFieldDescriptor &fieldDescriptor, std::ostream &output=std::cout) const
void PrintFieldTreeAsDot(std::ostream &output = std::cout) const
{
PrintFieldTreeAsDot(GetDescriptor().GetFieldZero(), output);
}
};
} // namespace Experimental
} // namespace ROOT
Expand Down
35 changes: 35 additions & 0 deletions tree/ntupleutil/v7/src/RNTupleInspector.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -516,3 +516,38 @@ ROOT::Experimental::RNTupleInspector::GetFieldsByName(const std::regex &fieldNam

return fieldIds;
}

void ROOT::Experimental::RNTupleInspector::PrintFieldTreeAsDot(const ROOT::RFieldDescriptor &fieldDescriptor,
std::ostream &output) const
{
const auto &tupleDescriptor = GetDescriptor();
const bool isZeroField = fieldDescriptor.GetParentId() == ROOT::kInvalidDescriptorId;
if (isZeroField) {
output << "digraph D {\n";
output << "node[shape=box]\n";
}
const std::string &nodeId = (isZeroField) ? "0" : std::to_string(fieldDescriptor.GetId() + 1);
const std::string &fieldName = (isZeroField) ? "RFieldZero" : fieldDescriptor.GetFieldName();
const std::string &description = fieldDescriptor.GetFieldDescription();
const std::uint32_t &version = fieldDescriptor.GetFieldVersion();

output << nodeId << "[label=<";
if (!isZeroField) {
output << "<b>Name: </b>" << fieldName << "<br></br>";
output << "<b>Type: </b>" << fieldDescriptor.GetTypeName() << "<br></br>";
output << "<b>ID: </b>" << std::to_string(fieldDescriptor.GetId()) << "<br></br>";
if (description != "")
output << "<b>Description: </b>" << description << "<br></br>";
if (version != 0)
output << "<b>Version: </b>" << version << "<br></br>";
} else
output << "<b>" << fieldName << "</b>";
output << ">]\n";
for (const auto &childFieldId : fieldDescriptor.GetLinkIds()) {
const auto &childFieldDescriptor = tupleDescriptor.GetFieldDescriptor(childFieldId);
output << nodeId + "->" + std::to_string(childFieldDescriptor.GetId() + 1) + "\n";
PrintFieldTreeAsDot(childFieldDescriptor, output);
}
if (isZeroField)
output << "}";
}
20 changes: 20 additions & 0 deletions tree/ntupleutil/v7/test/ntuple_inspector.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -813,3 +813,23 @@ TEST(RNTupleInspector, MultiColumnRepresentations)
EXPECT_EQ(ENTupleColumnType::kReal16, px1Inspector.GetType());
EXPECT_EQ(1u, px1Inspector.GetNElements());
}

TEST(RNTupleInspector, FieldTreeAsDot)
{
FileRaii fileGuard("test_ntuple_inspector_fields_tree_as_dot.root");
{
auto model = RNTupleModel::Create();
auto fldFloat1 = model->MakeField<float>("float1");
auto fldInt = model->MakeField<std::int32_t>("int");
auto writer = RNTupleWriter::Recreate(std::move(model), "ntuple", fileGuard.GetPath());
}
auto inspector = RNTupleInspector::Create("ntuple", fileGuard.GetPath());
std::ostringstream dotStream;
inspector->PrintFieldTreeAsDot(dotStream);
const std::string dot = dotStream.str();
const std::string &expected =
"digraph D {\nnode[shape=box]\n0[label=<<b>RFieldZero</b>>]\n0->1\n1[label=<<b>Name: "
"</b>float1<br></br><b>Type: </b>float<br></br><b>ID: </b>0<br></br>>]\n0->2\n2[label=<<b>Name: "
"</b>int<br></br><b>Type: </b>std::int32_t<br></br><b>ID: </b>1<br></br>>]\n}";
EXPECT_EQ(dot, expected);
}
Loading