diff --git a/tree/ntupleutil/v7/inc/ROOT/RNTupleInspector.hxx b/tree/ntupleutil/v7/inc/ROOT/RNTupleInspector.hxx index 42e6cf4437bfb..8617461a6e84d 100644 --- a/tree/ntupleutil/v7/inc/ROOT/RNTupleInspector.hxx +++ b/tree/ntupleutil/v7/inc/ROOT/RNTupleInspector.hxx @@ -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 /** @@ -93,7 +101,7 @@ public: : fColumnDescriptor(colDesc), fCompressedPageSizes(compressedPageSizes), fElementSize(elemSize), - fNElements(nElems) {}; + fNElements(nElems){}; ~RColumnInspector() = default; const ROOT::RColumnDescriptor &GetDescriptor() const { return fColumnDescriptor; } @@ -101,7 +109,8 @@ public: std::uint64_t GetNPages() const { return fCompressedPageSizes.size(); } std::uint64_t GetCompressedSize() const { - return std::accumulate(fCompressedPageSizes.begin(), fCompressedPageSizes.end(), static_cast(0)); + return std::accumulate(fCompressedPageSizes.begin(), fCompressedPageSizes.end(), + static_cast(0)); } std::uint64_t GetUncompressedSize() const { return fElementSize * fNElements; } std::uint64_t GetElementSize() const { return fElementSize; } @@ -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; } @@ -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 diff --git a/tree/ntupleutil/v7/src/RNTupleInspector.cxx b/tree/ntupleutil/v7/src/RNTupleInspector.cxx index 38fb2af2b6af2..7306b5511b39a 100644 --- a/tree/ntupleutil/v7/src/RNTupleInspector.cxx +++ b/tree/ntupleutil/v7/src/RNTupleInspector.cxx @@ -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 << "Name: " << fieldName << "

"; + output << "Type: " << fieldDescriptor.GetTypeName() << "

"; + output << "ID: " << std::to_string(fieldDescriptor.GetId()) << "

"; + if (description != "") + output << "Description: " << description << "

"; + if (version != 0) + output << "Version: " << version << "

"; + } else + output << "" << fieldName << ""; + 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 << "}"; +} diff --git a/tree/ntupleutil/v7/test/ntuple_inspector.cxx b/tree/ntupleutil/v7/test/ntuple_inspector.cxx index 96552b0c296a6..0ce07842c45c4 100644 --- a/tree/ntupleutil/v7/test/ntuple_inspector.cxx +++ b/tree/ntupleutil/v7/test/ntuple_inspector.cxx @@ -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("float1"); + auto fldInt = model->MakeField("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=<RFieldZero>]\n0->1\n1[label=<Name: " + "float1

Type: float

ID: 0

>]\n0->2\n2[label=<Name: " + "int

Type: std::int32_t

ID: 1

>]\n}"; + EXPECT_EQ(dot, expected); +}