From 9a4f0ce99f56ecbfff31d1583f7cfbabdc63dee7 Mon Sep 17 00:00:00 2001 From: lesimon Date: Thu, 21 Nov 2019 15:22:33 +0100 Subject: [PATCH 1/4] implement feature to draw storage layout --- tree/CMakeLists.txt | 1 + tree/ntuple/v7/inc/ROOT/RPage.hxx | 2 +- tree/ntupledraw/CMakeLists.txt | 29 + tree/ntupledraw/inc/LinkDef.h | 25 + tree/ntupledraw/v7/inc/ROOT/RDrawStorage.hxx | 179 ++++++ tree/ntupledraw/v7/src/RDrawStorage.cxx | 601 +++++++++++++++++++ tree/ntupledraw/v7/test/CMakeLists.txt | 9 + tree/ntupledraw/v7/test/ntupledraw.cxx | 146 +++++ 8 files changed, 991 insertions(+), 1 deletion(-) create mode 100644 tree/ntupledraw/CMakeLists.txt create mode 100644 tree/ntupledraw/inc/LinkDef.h create mode 100644 tree/ntupledraw/v7/inc/ROOT/RDrawStorage.hxx create mode 100644 tree/ntupledraw/v7/src/RDrawStorage.cxx create mode 100644 tree/ntupledraw/v7/test/CMakeLists.txt create mode 100644 tree/ntupledraw/v7/test/ntupledraw.cxx diff --git a/tree/CMakeLists.txt b/tree/CMakeLists.txt index dbcaa91ae6e2b..7adcca10627eb 100644 --- a/tree/CMakeLists.txt +++ b/tree/CMakeLists.txt @@ -9,3 +9,4 @@ add_subdirectory(treeplayer) add_subdirectory(treeviewer) add_subdirectory(dataframe) add_subdirectory(ntuple) +add_subdirectory(ntupledraw) diff --git a/tree/ntuple/v7/inc/ROOT/RPage.hxx b/tree/ntuple/v7/inc/ROOT/RPage.hxx index 43310e51a8285..a0ee21afa3caf 100644 --- a/tree/ntuple/v7/inc/ROOT/RPage.hxx +++ b/tree/ntuple/v7/inc/ROOT/RPage.hxx @@ -74,7 +74,7 @@ public: {} ~RPage() = default; - ColumnId_t GetColumnId() { return fColumnId; } + ColumnId_t GetColumnId() const { return fColumnId; } /// The total space available in the page ClusterSize_t::ValueType GetCapacity() const { return fCapacity; } /// The space taken by column elements in the buffer diff --git a/tree/ntupledraw/CMakeLists.txt b/tree/ntupledraw/CMakeLists.txt new file mode 100644 index 0000000000000..fd9126564bf93 --- /dev/null +++ b/tree/ntupledraw/CMakeLists.txt @@ -0,0 +1,29 @@ +# Copyright (C) 1995-2019, Rene Brun and Fons Rademakers. +# All rights reserved. +# +# For the licensing terms see $ROOTSYS/LICENSE. +# For the list of contributors see $ROOTSYS/README/CREDITS. + +############################################################################ +# CMakeLists.txt file for building ROOT ntupledraw package +# @author Simon Leisibach CERN +############################################################################ + +if(NOT root7) + return() +endif() + +ROOT_STANDARD_LIBRARY_PACKAGE(ROOTNTupleDraw +HEADERS + ROOT/RDrawStorage.hxx +SOURCES + v7/src/RDrawStorage.cxx +LINKDEF + LinkDef.h +DEPENDENCIES + ROOTNTuple + Hist + Gpad +) + +ROOT_ADD_TEST_SUBDIRECTORY(v7/test) diff --git a/tree/ntupledraw/inc/LinkDef.h b/tree/ntupledraw/inc/LinkDef.h new file mode 100644 index 0000000000000..a9359da9a4e0f --- /dev/null +++ b/tree/ntupledraw/inc/LinkDef.h @@ -0,0 +1,25 @@ +// Author: Simon Leisibach CERN 11/2019 + +/************************************************************************* + * Copyright (C) 1995-2019, Rene Brun and Fons Rademakers. * + * All rights reserved. * + * * + * For the licensing terms see $ROOTSYS/LICENSE. * + * For the list of contributors see $ROOTSYS/README/CREDITS. * + *************************************************************************/ + +#ifdef __CLING__ + +#pragma link off all globals; +#pragma link off all classes; +#pragma link off all functions; + +#pragma link C++ nestedtypedefs; +#pragma link C++ nestedclasses; + +// Support for auto-loading +#pragma link C++ class ROOT::Experimental::RNTupleDraw-; +#pragma link C++ class ROOT::Experimental::Detail::RMetaDataBox+; +#pragma link C++ class ROOT::Experimental::Detail::RPageBox+; + +#endif diff --git a/tree/ntupledraw/v7/inc/ROOT/RDrawStorage.hxx b/tree/ntupledraw/v7/inc/ROOT/RDrawStorage.hxx new file mode 100644 index 0000000000000..d71c3c92fb31d --- /dev/null +++ b/tree/ntupledraw/v7/inc/ROOT/RDrawStorage.hxx @@ -0,0 +1,179 @@ +/// \file ROOT/RDrawStorage.hxx +/// \ingroup NTupleDraw ROOT7 +/// \author Simon Leisibach +/// \date 2019-11-07 +/// \warning This is part of the ROOT 7 prototype! It will change without notice. It might trigger earthquakes. Feedback +/// is welcome! + +/************************************************************************* + * Copyright (C) 1995-2019, Rene Brun and Fons Rademakers. * + * All rights reserved. * + * * + * For the licensing terms see $ROOTSYS/LICENSE. * + * For the list of contributors see $ROOTSYS/README/CREDITS. * + *************************************************************************/ + +#ifndef ROOT7_RDrawStorage +#define ROOT7_RDrawStorage + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace ROOT { +namespace Experimental { +namespace Detail { +class RDrawStorage; + +// clang-format off +/** +\class ROOT::Experimental::Detail::RMetaDataBox +\ingroup NTupleDraw +\brief A TBox which contains metadata information of a RNTuple + +A RMetaDataBox is drawn on the TCanvas showing the RNTuple storage layout and represents some metadata (header or footer) in the RNTuple. It also holds some data of the metadata it represents, like its byte size. +*/ +// clang-format on +class RMetaDataBox : public TBox { +private: + std::string fDescription; // e.g. "Header" or "Footer" + const std::uint32_t fNBytesInStorage; + RDrawStorage *fParent; + +public: + RMetaDataBox() : RMetaDataBox(0, 0, 0, 0, "", 0, nullptr) {} + RMetaDataBox(double x1, double y1, double x2, double y2, std::string description, std::uint32_t nBytes, + RDrawStorage *parent, std::int32_t color = kGray); + std::uint32_t GetNBytesInStorage() const { return fNBytesInStorage; } + void Dump() const final; // *MENU* + void Inspect() const final; // *MENU* + ClassDef(RMetaDataBox, 1) +}; + +// clang-format off +/** +\class ROOT::Experimental::RPageBox +\ingroup NTupleDraw +\brief A TBox which represents a RPage + +A RPageBox is drawn on the TCanvas showing the RNTuple storage layout and represents a RPage in the RNTuple. It also holds various data of a RPage, which allows the user to dump/inspect the RPageBox to obtain information about the RPage. +*/ +// clang-format on +class RPageBox : public TBox { +private: + std::string fFieldName; + std::string fFieldType; + std::string fColumnType; + DescriptorId_t fFieldId; + DescriptorId_t fColumnId; + DescriptorId_t fClusterId; + ClusterSize_t::ValueType fNElements; + ClusterSize_t::ValueType fElementSizeOnDisk; + NTupleSize_t fGlobalRangeStart; + NTupleSize_t fClusterRangeStart; + RClusterDescriptor::RLocator fLocator; // required for sorting + RDrawStorage *fParent; + std::size_t fPageBoxId; + +public: + RPageBox() + : RPageBox(0, 0, 0, 0, "", "", 0, 0, 0, EColumnType::kUnknown, 0, 0, 0, RClusterDescriptor::RLocator(), nullptr) + { + } + RPageBox(double x1, double y1, double x2, double y2, std::string fieldName, std::string fieldType, + DescriptorId_t fieldId, DescriptorId_t columnId, DescriptorId_t clusterId, EColumnType columnType, + ClusterSize_t::ValueType nElements, NTupleSize_t globalRangeStart, NTupleSize_t clusterRangeStart, + RClusterDescriptor::RLocator locator, RDrawStorage *parent, std::size_t pageBoxId = 0); + DescriptorId_t GetFieldId() const { return fFieldId; } + DescriptorId_t GetClusterId() const { return fClusterId; } + const RClusterDescriptor::RLocator &GetLocator() const { return fLocator; } + void SetPageId(std::size_t pageId) { fPageBoxId = pageId; } + void Dump() const final; // *MENU* + void Inspect() const final; // *MENU* + ClassDef(RPageBox, 1) +}; + +// clang-format off +/** +\class ROOT::Experimental::RDrawStorage +\ingroup NTupleDraw +\brief Main class for drawing the storage layout of a RNTuple + +This class coordinates the drawing process of the storage layout of a RNTuple. It also holds all generated unique pointers with a static member variable until ROOT is terminated, in order for the drawing to persist. +*/ +// clang-format on +class RDrawStorage { +private: + std::size_t fNEntries; + std::size_t fTotalNumBytes; + // E.g. if the total number of bytes in a file is 42*1024*1024 bytes = 42 MB, then fScalingFactorOfAxis is set to + // 1024*1024, so that 42 can be displayed on the axis. + std::size_t fScalingFactorOfAxis = 1; + NTupleSize_t fNFields; + NTupleSize_t fNColumns; + NTupleSize_t fNClusters; + /// For every page in the ntuple, there exists an entry in fPageBoxes. + std::vector> fPageBoxes; + std::vector> fTexts; + std::vector> fLines; + std::vector> fPads; + std::vector> fTH1Fs; + std::string fNTupleName; + std::string fAxisTitle; // for TLatex in RDrawStorage::Draw() + std::unique_ptr fHeaderBox; + std::unique_ptr fFooterBox; + std::unique_ptr fLegend; + void SetPageIds(); + +public: + // holds all generated cavas pointers in case they need to be manually destructed in the future due to changes in + // ROOT. + std::vector fCanvasPtrs; + RDrawStorage(RNTupleReader *reader); + /// holds all created RDrawStorage instances until the lifetime + static std::vector fgDrawStorageVec; + static std::int32_t GetColourFromFieldId(DescriptorId_t fieldId); + std::size_t GetPageBoxSize() const { return fPageBoxes.size(); } + std::size_t GetTotalNumBytes() const { return fTotalNumBytes; } + std::size_t GetScalingFactorOfAxis() const { return fScalingFactorOfAxis; } + NTupleSize_t GetNFields() const { return fNFields; } + NTupleSize_t GetNColumns() const { return fNColumns; } + NTupleSize_t GetNClusters() const { return fNClusters; } + static void RPageBoxClicked(); + void Draw(); +}; +} // namespace Detail + +// clang-format off +/** +\class ROOT::Experimental::RNTupleDraw +\ingroup NTupleDraw +\brief User interface for drawing the structure of an ntuple + +This class acts as a user interface like RNTupleWriter and RNTupleReader. It acts as a delegeator to RDrawStorage instead of doing the drawing job itself. This way the lifetime of the data displayed on the canvas is tied to the termination of the ROOT program and not to the destruction of a RNTupleDraw or RNTupleReader instance. +*/ +// clang-format on +class RNTupleDraw { +private: + Detail::RDrawStorage *fStorage; + bool fEmpty = false; +public: + RNTupleDraw(const std::unique_ptr &reader); + static std::unique_ptr Open(const std::unique_ptr &reader); + void Draw(); +}; +} // namespace Experimental +} // namespace ROOT + +#endif diff --git a/tree/ntupledraw/v7/src/RDrawStorage.cxx b/tree/ntupledraw/v7/src/RDrawStorage.cxx new file mode 100644 index 0000000000000..20d147642505f --- /dev/null +++ b/tree/ntupledraw/v7/src/RDrawStorage.cxx @@ -0,0 +1,601 @@ +/// \file RDrawStorage.cxx +/// \ingroup NTupleDraw ROOT7 +/// \author Simon Leisibach +/// \date 2019-11-07 +/// \warning This is part of the ROOT 7 prototype! It will change without notice. It might trigger earthquakes. Feedback +/// is welcome! + +/************************************************************************* + * Copyright (C) 1995-2019, Rene Brun and Fons Rademakers. * + * All rights reserved. * + * * + * For the licensing terms see $ROOTSYS/LICENSE. * + * For the list of contributors see $ROOTSYS/README/CREDITS. * + *************************************************************************/ + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +std::vector ROOT::Experimental::Detail::RDrawStorage::fgDrawStorageVec; + +// ------------------------------ RMetaDataBox ------------------------------ + +ROOT::Experimental::Detail::RMetaDataBox::RMetaDataBox(double x1, double y1, double x2, double y2, + std::string description, std::uint32_t nBytes, + RDrawStorage *parent, std::int32_t color) + : TBox(x1, y1, x2, y2), fDescription{description}, fNBytesInStorage{nBytes}, fParent{parent} +{ + SetFillColor(color); +} + +void ROOT::Experimental::Detail::RMetaDataBox::Dump() const +{ + std::cout << " ==> Dumping Page information:\n\n"; + std::cout << "Description: \t\t" << fDescription << std::endl; + std::cout << "Size: \t\t" << fNBytesInStorage << " bytes" << std::endl; +} + +void ROOT::Experimental::Detail::RMetaDataBox::Inspect() const +{ + static std::int32_t index = 0; + // The canvases need to have unique names, or else there will be an error saying that not all were found when trying + // to delete them when quitting the program. + std::string uniqueCanvasName{"MetaDataDetails" + std::to_string(++index)}; + + fParent->fCanvasPtrs.emplace_back(new TCanvas(uniqueCanvasName.c_str(), "Page Details", 500, 300)); + TLatex latex; + + // Draw Title + latex.SetTextAlign(12); + latex.SetTextSize(0.08); + latex.DrawLatex(0.01, 0.96, fDescription.c_str()); + + // Write Details + latex.SetTextSize(0.06); + std::string sizeString = "Size:" + std::string(30, ' ') + std::to_string(fNBytesInStorage) + " bytes"; + latex.DrawLatex(0.01, 0.85, sizeString.c_str()); + fParent->fCanvasPtrs.back()->Update(); +} + +ClassImp(ROOT::Experimental::Detail::RMetaDataBox) + + // ------------------------------ RPageBox ---------------------------------- + + void ROOT::Experimental::Detail::RPageBox::Dump() const +{ + std::cout << " ==> Dumping Page information:\n\n"; + std::cout << "Page Id: \t\t" << fPageBoxId << " / " << fParent->GetPageBoxSize() << std::endl; + std::cout << "Cluster Id: \t\t" << fClusterId << " / " << fParent->GetNClusters() - 1 << std::endl; + std::cout << "Field Id: \t\t" << fFieldId << " / " << fParent->GetNFields() - 1 << std::endl; + std::cout << "FieldName: \t\t" << fFieldName << std::endl; + std::cout << "FieldType: \t\t" << fFieldType << std::endl; + std::cout << "Column Id: \t\t" << fColumnId << " / " << fParent->GetNColumns() - 1 << std::endl; + std::cout << "ColumnType: \t\t" << fColumnType << std::endl; + std::cout << "NElements: \t\t" << fNElements << std::endl; + std::cout << "Element Size On Disk: \t\t" << fElementSizeOnDisk << " bits" << std::endl; + std::cout << "Element Size On Storage:\t\t" << 8 * fLocator.fBytesOnStorage / fNElements << " bits" << std::endl; + std::cout << "Page Size On Disk: \t\t" << fNElements * fElementSizeOnDisk / 8 << " bytes" << std::endl; + std::cout << "Page Size On Storage: \t\t" << fLocator.fBytesOnStorage << " bytes" << std::endl; + std::cout << "Global Page Range: \t\t" << fGlobalRangeStart << " - " << fGlobalRangeStart + fNElements - 1 + << std::endl; + std::cout << "Cluster Page Range: \t\t" << fClusterRangeStart << " - " << fClusterRangeStart + fNElements - 1 + << std::endl; + std::size_t totalNumBytes = fParent->GetTotalNumBytes(); + std::size_t scalingFactorOfAxis = fParent->GetScalingFactorOfAxis(); + std::cout.setf(std::ios::fixed); + std::cout << "Location in Storage: \t\t" << static_cast(GetX1() * scalingFactorOfAxis) << " / " + << totalNumBytes << " bytes" << std::endl; + std::cout.unsetf(std::ios::fixed); +} + +void ROOT::Experimental::Detail::RPageBox::Inspect() const +{ + static std::int32_t index = 0; + // The canvases need to have unique names, or else there will be an error saying that not all were found when trying + // to delete them when quitting the program. + std::string uniqueCanvasName{"PageDetails" + std::to_string(++index)}; + fParent->fCanvasPtrs.emplace_back(new TCanvas(uniqueCanvasName.c_str(), "Page Details", 500, 300)); + + TLatex latex; + // Draw Title + latex.SetTextAlign(12); + latex.SetTextSize(0.08); + std::string pageNumbering = + "Page No. " + std::to_string(fPageBoxId) + " / " + std::to_string(fParent->GetPageBoxSize()); + latex.DrawLatex(0.01, 0.96, pageNumbering.c_str()); + + // Write details about page + latex.SetTextSize(0.06); + std::string clusterIdString = "Cluster Id:" + std::string(37, ' ') + std::to_string(fClusterId) + " / " + + std::to_string(fParent->GetNClusters() - 1); + latex.DrawLatex(0.01, 0.85, clusterIdString.c_str()); + std::string fieldIdString = + "Field Id:" + std::string(41, ' ') + std::to_string(fFieldId) + " / " + std::to_string(fParent->GetNFields() - 1); + latex.DrawLatex(0.01, 0.80, fieldIdString.c_str()); + std::string fieldName = "FieldName:" + std::string(35, ' ') + fFieldName; + latex.DrawLatex(0.01, 0.75, fieldName.c_str()); + std::string fieldType = "FieldType:" + std::string(37, ' ') + fFieldType; + latex.DrawLatex(0.01, 0.70, fieldType.c_str()); + std::string columnIdString = "Column Id:" + std::string(36, ' ') + std::to_string(fColumnId) + " / " + + std::to_string(fParent->GetNColumns() - 1); + latex.DrawLatex(0.01, 0.65, columnIdString.c_str()); + std::string columnTypeString = "ColumnType:" + std::string(32, ' ') + fColumnType; + latex.DrawLatex(0.01, 0.60, columnTypeString.c_str()); + std::string nElements = "NElements:" + std::string(35, ' ') + std::to_string(fNElements); + latex.DrawLatex(0.01, 0.55, nElements.c_str()); + std::string elementSizeOnDisk = + "Element Size On Disk:" + std::string(17, ' ') + std::to_string(fElementSizeOnDisk) + " bits"; + latex.DrawLatex(0.01, 0.50, elementSizeOnDisk.c_str()); + std::string elementSizeOnStorage = "Element Size On Storage:" + std::string(11, ' ') + + std::to_string(8 * fLocator.fBytesOnStorage / fNElements) + " bits"; + latex.DrawLatex(0.01, 0.45, elementSizeOnStorage.c_str()); + std::string pageSize = + "Page Size On Disk:" + std::string(22, ' ') + std::to_string(fNElements * fElementSizeOnDisk / 8) + " bytes"; + latex.DrawLatex(0.01, 0.40, pageSize.c_str()); + std::string pageSizeStorage = + "Page Size On Storage:" + std::string(16, ' ') + std::to_string(fLocator.fBytesOnStorage) + " bytes"; + latex.DrawLatex(0.01, 0.35, pageSizeStorage.c_str()); + std::string globalRange = "Global Page Range:" + std::string(21, ' ') + std::to_string(fGlobalRangeStart) + " - " + + std::to_string(fGlobalRangeStart + fNElements - 1); + latex.DrawLatex(0.01, 0.30, globalRange.c_str()); + std::string clusterRange = "Cluster Page Range:" + std::string(20, ' ') + std::to_string(fClusterRangeStart) + + " - " + std::to_string(fClusterRangeStart + fNElements - 1); + latex.DrawLatex(0.01, 0.25, clusterRange.c_str()); + std::size_t totalNumBytes = fParent->GetTotalNumBytes(); + std::size_t scalingFactorOfAxis = fParent->GetScalingFactorOfAxis(); + std::string locationString = "Location in Storage:" + std::string(20, ' ') + + std::to_string(static_cast(GetX1() * scalingFactorOfAxis)) + " / " + + std::to_string(totalNumBytes) + " bytes"; + latex.DrawLatex(0.01, 0.20, locationString.c_str()); + + fParent->fCanvasPtrs.back()->Update(); +} + +ClassImp(ROOT::Experimental::Detail::RPageBox) + + // ------------------------------ RDrawStorage ------------------------------ + + ROOT::Experimental::Detail::RPageBox::RPageBox(double x1, double y1, double x2, double y2, std::string fieldName, + std::string fieldType, DescriptorId_t fieldId, + DescriptorId_t columnId, DescriptorId_t clusterId, + EColumnType columnType, ClusterSize_t::ValueType nElements, + NTupleSize_t globalRangeStart, NTupleSize_t clusterRangeStart, + RClusterDescriptor::RLocator locator, RDrawStorage *parent, + std::size_t pageBoxId) + : TBox(x1, y1, x2, y2), fFieldName{fieldName}, fFieldType{fieldType}, fFieldId{fieldId}, fColumnId{columnId}, + fClusterId{clusterId}, fNElements{nElements}, fGlobalRangeStart{globalRangeStart}, + fClusterRangeStart{clusterRangeStart}, fLocator{locator}, fParent{parent}, fPageBoxId{pageBoxId} +{ + switch (columnType) { + case EColumnType::kIndex: + fColumnType = "Index"; + fElementSizeOnDisk = sizeof(ClusterSize_t) * 8; + break; + case EColumnType::kSwitch: + fColumnType = "Switch"; + fElementSizeOnDisk = sizeof(ROOT::Experimental::RColumnSwitch) * 8; + break; + case EColumnType::kByte: + fColumnType = "Byte"; + fElementSizeOnDisk = sizeof(char) * 8; + break; + case EColumnType::kBit: + fColumnType = "Bit"; + fElementSizeOnDisk = sizeof(bool) * 8; + break; + case EColumnType::kReal64: + fColumnType = "Real64"; + fElementSizeOnDisk = sizeof(double) * 8; + break; + case EColumnType::kReal32: + fColumnType = "Real32"; + fElementSizeOnDisk = sizeof(float) * 8; + break; + // Uncomment after implementing custom-sized float-packing. + /*case EColumnType::kReal24: + fColumnType = "Real24"; + fElementSizeOnDisk = 24; + break; + case EColumnType::kCustomDouble: + fColumnType = "CustomDouble"; + fElementSizeOnDisk = sizeof(double)*8; + break; + case EColumnType::kCustomFloat: + fColumnType = "CustomFloat"; + fElementSizeOnDisk = sizeof(float)*8; + break;*/ + case EColumnType::kReal16: + fColumnType = "Real16"; + fElementSizeOnDisk = 16; + break; + case EColumnType::kReal8: + fColumnType = "Real8"; + fElementSizeOnDisk = 8; + break; + case EColumnType::kInt64: + fColumnType = "Int64"; + fElementSizeOnDisk = 64; + break; + case EColumnType::kInt32: + fColumnType = "Int32"; + fElementSizeOnDisk = 32; + break; + case EColumnType::kInt16: + fColumnType = "Int16"; + fElementSizeOnDisk = 16; + break; + case EColumnType::kUnknown: + fColumnType = "kUnknown"; + fElementSizeOnDisk = -1; + break; + default: assert(false); + } +} + +ROOT::Experimental::Detail::RDrawStorage::RDrawStorage(ROOT::Experimental::RNTupleReader *reader) +{ + // RDrawStorage::Draw() should work without the descriptor because its lifetime is not coupled to this object. The + // job of this constructor is to get all information necessary for drawing from the descriptor, so that it isn't + // required later anymore. + auto &desc = reader->GetDescriptor(); + + /* Procedure: + * 1. Check for special cases like empty ntuple + * 2. Prepare Title and TLegend + * 3. Create all boxes and colour them + * 4. Sort RPageBoxes by page order and set Page Ids + * 5. Create CumulativeNBytes to later to set x1 and x2 values for the boxes + * 6. Prepare StorageSizeAxis + * 7. Set x1 and x2 values for all boxes + * 8. Include box entries in the legend + * 9. Prepare ClusterAxis + */ + + // 1. Check for special cases like empty ntuple + fNFields = desc.GetNFields(); + fNEntries = desc.GetNEntries(); + if (fNFields <= 1 || fNEntries <= 0) + return; + + // 2. Prepare Title and TLegend + fNTupleName = desc.GetName(); + std::string title = "Storage layout of " + fNTupleName; + fTexts.emplace_back(std::make_unique(.5, .94, title.c_str())); + fTexts.back()->SetTextAlign(22); + fTexts.back()->SetTextSize(0.08); + + fLegend = std::make_unique(0.05, 0.05, .95, .55); + std::int32_t nColumnsInLegend = 2; + if (fNFields > 150) { + nColumnsInLegend = 10; + } else if (fNFields > 120) { + nColumnsInLegend = 9; + } else if (fNFields > 100) { + nColumnsInLegend = 8; + } else if (fNFields > 75) { + nColumnsInLegend = 7; + } else if (fNFields > 33) { + nColumnsInLegend = 6; + } else if (fNFields > 26) { + nColumnsInLegend = 5; + } else if (fNFields > 19) { + nColumnsInLegend = 4; + } else if (fNFields > 4) { + nColumnsInLegend = 3; + } + fLegend->SetNColumns(nColumnsInLegend); + + // 3. Create all boxes and colour them + constexpr double boxY1 = 0; + constexpr double boxY2 = 1; + fHeaderBox = + std::make_unique(0.0, boxY1, 0.0, boxY2, "Header", desc.SerializeHeader(nullptr), this, kGray); + fFooterBox = + std::make_unique(0.0, boxY1, 0.0, boxY2, "Footer", desc.SerializeFooter(nullptr), this, kGray); + fNColumns = desc.GetNColumns(); + fNClusters = desc.GetNClusters(); + for (std::size_t i = 0; i < fNClusters; ++i) { + for (std::size_t j = 0; j < fNColumns; ++j) { + ClusterSize_t::ValueType localIndex{0}; + auto &pageRange = desc.GetClusterDescriptor(i).GetPageRange(j); + for (std::size_t k = 0; k < pageRange.fPageInfos.size(); ++k) { + // Only use the descriptor to obtain information about a page + DescriptorId_t fieldId = desc.GetColumnDescriptor(j).GetFieldId(); + std::string fieldName = desc.GetFieldDescriptor(fieldId).GetFieldName(); + std::string fieldType = desc.GetFieldDescriptor(fieldId).GetTypeName(); + EColumnType columnType = desc.GetColumnDescriptor(j).GetModel().GetType(); + ClusterSize_t::ValueType nElements = pageRange.fPageInfos.at(k).fNElements; + auto clusterRangeFirst = localIndex; + auto globalRangeFirst = localIndex + desc.GetClusterDescriptor(i).GetColumnRange(j).fFirstElementIndex; + + fPageBoxes.emplace_back(std::make_unique( + 0, boxY1, 0, boxY2, fieldName, fieldType, fieldId, j, i, columnType, nElements, globalRangeFirst, + clusterRangeFirst, pageRange.fPageInfos.at(k).fLocator, this)); + fPageBoxes.back()->SetFillColor(GetColourFromFieldId(fieldId)); + + localIndex += nElements; + } + } + } + + // 4. Sort RPageBoxes by page order and set Page Ids + std::sort(fPageBoxes.begin(), fPageBoxes.end(), + [](const std::unique_ptr &a, const std::unique_ptr &b) -> bool { + if (a->GetClusterId() != b->GetClusterId()) + return a->GetClusterId() < b->GetClusterId(); + return a->GetLocator().fPosition < b->GetLocator().fPosition; + }); + SetPageIds(); + + // 5. Create CumulativeNBytes to later to set x1 and x2 values for the boxes + // Size is +2, because +1 for header and +1 for footer + std::vector cumulativeNBytes(fPageBoxes.size() + 2); + cumulativeNBytes.at(0) = fHeaderBox->GetNBytesInStorage(); + for (std::size_t i = 1; i < cumulativeNBytes.size() - 1; ++i) { + cumulativeNBytes.at(i) = cumulativeNBytes.at(i - 1) + fPageBoxes.at(i - 1)->GetLocator().fBytesOnStorage; + } + cumulativeNBytes.back() = fFooterBox->GetNBytesInStorage() + cumulativeNBytes.at(cumulativeNBytes.size() - 2); + + // 6. Prepare StorageSizeAxis + fAxisTitle = "#splitline{Data}{#splitline{Size}{#splitlie{in}{Bytes}}}"; + fTotalNumBytes = cumulativeNBytes.back(); + fScalingFactorOfAxis = 1; + if (fTotalNumBytes > 1024 * 1024 * 1024) { + fScalingFactorOfAxis = 1024 * 1024 * 1024; + fAxisTitle = "#splitline{Data}{#splitline{Size}{in GB}}"; + } else if (fTotalNumBytes > 1024 * 1024) { + fScalingFactorOfAxis = 1024 * 1024; + fAxisTitle = "#splitline{Data}{#splitline{Size}{in MB}}"; + } else if (fTotalNumBytes > 1024) { + fScalingFactorOfAxis = 1024; + fAxisTitle = "#splitline{Data}{#splitline{Size}{in KB}}"; + } + + // 7. Set x1 and x2 values for all boxes + fHeaderBox->SetX1(0); + fHeaderBox->SetX2((double)cumulativeNBytes.at(0) / fScalingFactorOfAxis); + for (std::size_t i = 0; i < fPageBoxes.size(); ++i) { + fPageBoxes.at(i)->SetX1((double)cumulativeNBytes.at(i) / fScalingFactorOfAxis); + fPageBoxes.at(i)->SetX2((double)cumulativeNBytes.at(i + 1) / fScalingFactorOfAxis); + } + fFooterBox->SetX1((double)cumulativeNBytes.at(cumulativeNBytes.size() - 2) / fScalingFactorOfAxis); + fFooterBox->SetX2((double)cumulativeNBytes.back() / fScalingFactorOfAxis); + + // 8. Include box entries in the legend + fLegend->AddEntry(fHeaderBox.get(), "Header", "f"); + // start at 1 to skip rootField + for (std::size_t i = 1; i < fNFields; ++i) { + // for each field find the first PageBox which represents that field and add it to the legend + auto vecIt = std::find_if(fPageBoxes.begin(), fPageBoxes.end(), + [i](const std::unique_ptr &a) -> bool { return a->GetFieldId() == i; }); + if (vecIt != fPageBoxes.end()) { + fLegend->AddEntry((*vecIt).get(), desc.GetFieldDescriptor(i).GetFieldName().c_str(), "f"); + } + } + fLegend->AddEntry(fFooterBox.get(), "Footer", "f"); + + // 9. Prepare ClusterAxis + double distanceBetweenLines = 0.001 * (fTotalNumBytes / fScalingFactorOfAxis); + std::size_t start = cumulativeNBytes.at(0); + std::size_t end{0}; + for (std::size_t i = 0; i < fNClusters; ++i) { + auto &cluster = desc.GetClusterDescriptor(i); + std::size_t nBytes = cluster.GetLocator().fBytesOnStorage; + // For some data formats (e.g. root-Files) this value is equal to 0. In that case get nBytes manually from all + // columns. + if (nBytes == 0) { + for (std::size_t j = 0; j < fNColumns; ++j) { + for (std::size_t k = 0; k < cluster.GetPageRange(j).fPageInfos.size(); ++k) { + nBytes += cluster.GetPageRange(j).fPageInfos.at(k).fLocator.fBytesOnStorage; + } + } + } + end = start + nBytes; + double x1 = (double)start / fScalingFactorOfAxis + distanceBetweenLines / 2; + double x2 = (double)end / fScalingFactorOfAxis - distanceBetweenLines / 2; + fLines.emplace_back(std::make_unique(x1, 1.05, x2, 1.05)); + fLines.back()->SetLineWidth(3); + fTexts.emplace_back(std::make_unique((x1 + x2) / 2, 1.2, std::to_string(i).c_str())); + fTexts.back()->SetTextAlign(22); + fTexts.back()->SetTextSize(0.15); + start = end; + } + fLegend->AddEntry(fLines.back().get(), "Cluster Id", "l"); +} + +void ROOT::Experimental::Detail::RDrawStorage::Draw() +{ + /* Procedure: + * 1. Check for special cases like empty ntuple + * 2. Create a new canvas + * 3. Create a TPad in the canvas so that when zooming only the boxes and axis get zoomed + * 4. Draw an empty histogram without a y-axis for zooming + * 5. Draw all boxes and add possibility to click on RPageBox to obtain information about a page + * 6. Draw clusterAxis + * 7. Return to canvas, draw title, legend and description of x-axis + */ + + // 1. Check for special cases like empty ntuple + if (fNFields <= 1) { + std::cout << "The ntuple has no fields. No storage layout was drawn." << std::endl; + return; + } + if (fNEntries <= 0) { + std::cout << "The ntuple has no entries. No storage layout was drawn." << std::endl; + return; + } + + // 2. Create a new canvas + static std::int32_t uniqueId = 0; + // Trying to delete multiple canvases with the same name leads to an error or when two canvases have the same name, only 1 may get deleted, causing a memory leak. + std::string uniqueCanvasName = "RDrawStorage" + std::to_string(++uniqueId); + fCanvasPtrs.emplace_back(new TCanvas(uniqueCanvasName.c_str(), fNTupleName.c_str(), 1000, 300)); + + // 3. Create a TPad in the canvas so that when zooming only the boxes and axis get zoomed + constexpr double marginlength = 0.03; + std::string uniquePadName = "RDrawStoragePad" + std::to_string(uniqueId); + fPads.emplace_back(std::make_unique(uniquePadName.c_str(), "", marginlength, 0.55, 1 - marginlength, 0.87)); + fPads.back()->SetTopMargin(0.2); + fPads.back()->SetBottomMargin(0.2); + fPads.back()->SetLeftMargin(0.01); + fPads.back()->SetRightMargin(0.01); + fPads.back()->Draw(); + fPads.back()->cd(); + + // 4. Draw an empty histogram without a y-axis for zooming + std::string uniqueTH1FName = "RDrawStorageTH1F" + std::to_string(uniqueId); + fTH1Fs.emplace_back(std::make_unique(uniqueTH1FName.c_str(), "", 500, 0, (double)fTotalNumBytes / fScalingFactorOfAxis)); + fTH1Fs.back()->SetMaximum(1); + fTH1Fs.back()->SetMinimum(0); + fTH1Fs.back()->GetYaxis()->SetTickLength(0); + fTH1Fs.back()->GetYaxis()->SetLabelSize(0); + fTH1Fs.back()->GetXaxis()->SetLabelSize(0.18); + fTH1Fs.back()->SetStats(0); + fTH1Fs.back()->DrawCopy(); + + // 5. Draw all boxes and add possibility to click on RPageBox to obtain information about a page + fHeaderBox->Draw(); + for (const auto &b : fPageBoxes) { + b->Draw(); + } + fFooterBox->Draw(); + fPads.back()->AddExec("ShowPageDetails", "ROOT::Experimental::Detail::RDrawStorage::RPageBoxClicked()"); + + // 6. Draw clusterAxis + // fTexts.at(0) points to Title so skip + for (std::size_t i = 1; i < fTexts.size(); ++i) { + fTexts.at(i)->Draw(); + } + for (const auto &l : fLines) { + l->Draw(); + } + fPads.back()->Update(); + + // 7. Return to canvas, draw title, legend and description of x-axis + fCanvasPtrs.back()->cd(); + fTexts.at(0)->Draw(); // Title + fLegend->Draw(); + TLatex latex; + latex.SetTextSize(0.04); + latex.DrawLatex(0.955, 0.5, fAxisTitle.c_str()); + + fCanvasPtrs.back()->Update(); +} + +void ROOT::Experimental::Detail::RDrawStorage::SetPageIds() +{ + for (std::size_t i = 0; i < fPageBoxes.size(); ++i) { + fPageBoxes.at(i)->SetPageId(i + 1); + } +} + +void ROOT::Experimental::Detail::RDrawStorage::RPageBoxClicked() +{ + int event = gPad->GetEvent(); + if (event != kButton1Up) + return; + TObject *select = gPad->GetSelected(); + if (!select) + return; + if (select->InheritsFrom(ROOT::Experimental::Detail::RPageBox::Class())) { + ROOT::Experimental::Detail::RPageBox *pageBox = (ROOT::Experimental::Detail::RPageBox *)select; + pageBox->Inspect(); + } else if (select->InheritsFrom(ROOT::Experimental::Detail::RMetaDataBox::Class())) { + ROOT::Experimental::Detail::RMetaDataBox *metaBox = (ROOT::Experimental::Detail::RMetaDataBox *)select; + metaBox->Inspect(); + } +} + +std::int32_t ROOT::Experimental::Detail::RDrawStorage::GetColourFromFieldId(ROOT::Experimental::DescriptorId_t fieldId) +{ + std::int32_t colour = 0; + fieldId %= 61; + switch (fieldId % 12) { + case 0: colour = kRed; break; + case 1: colour = kMagenta; break; + case 2: colour = kBlue; break; + case 3: colour = kCyan; break; + case 4: colour = kGreen; break; + case 5: colour = kYellow; break; + case 6: colour = kPink; break; + case 7: colour = kViolet; break; + case 8: colour = kAzure; break; + case 9: colour = kTeal; break; + case 10: colour = kSpring; break; + case 11: colour = kOrange; break; + default: + // never here + assert(false); + break; + } + switch (fieldId / 12) { + case 0: colour -= 2; break; + case 1: break; + case 2: colour += 3; break; + case 3: colour -= 6; break; + case 4: colour -= 9; break; + case 5: + if (fieldId == 60) + return kGray; + default: + // never here + assert(false); + break; + } + return colour; +} + +// ------------------------------ RNTupleDraw ------------------------------- + +ROOT::Experimental::RNTupleDraw::RNTupleDraw(const std::unique_ptr &reader) +{ + if (reader == nullptr) { + std::cout << "The RNTupleReader is invalid! Drawing is not possible with this object." << std::endl; + fEmpty = true; + } + Detail::RDrawStorage::fgDrawStorageVec.emplace_back(reader.get()); + fStorage = &Detail::RDrawStorage::fgDrawStorageVec.back(); +} + +std::unique_ptr +ROOT::Experimental::RNTupleDraw::Open(const std::unique_ptr &reader) +{ + if (reader == nullptr) { + std::cout << "The RNTupleReader is invalid, a nullptr was returned." << std::endl; + return nullptr; + } + return std::make_unique(reader); +} + +void ROOT::Experimental::RNTupleDraw::Draw() +{ + if (fEmpty == false) { + fStorage->Draw(); + } else { + std::cout << "Cannot draw object from an empty storage." << std::endl; + } +} diff --git a/tree/ntupledraw/v7/test/CMakeLists.txt b/tree/ntupledraw/v7/test/CMakeLists.txt new file mode 100644 index 0000000000000..791c7b21ab20b --- /dev/null +++ b/tree/ntupledraw/v7/test/CMakeLists.txt @@ -0,0 +1,9 @@ +# Copyright (C) 1995-2019, Rene Brun and Fons Rademakers. +# All rights reserved. +# +# For the licensing terms see $ROOTSYS/LICENSE. +# For the list of contributors see $ROOTSYS/README/CREDITS. + +# @author Simon Leisibach CERN + +ROOT_ADD_GTEST(ntupledraw ntupledraw.cxx LIBRARIES ROOTNTupleDraw) diff --git a/tree/ntupledraw/v7/test/ntupledraw.cxx b/tree/ntupledraw/v7/test/ntupledraw.cxx new file mode 100644 index 0000000000000..b2dfafc686c62 --- /dev/null +++ b/tree/ntupledraw/v7/test/ntupledraw.cxx @@ -0,0 +1,146 @@ +#include +#include +#include +#include + +#include + +#include "gtest/gtest.h" + +#include +#include + +using RNTupleReader = ROOT::Experimental::RNTupleReader; +using RNTupleWriter = ROOT::Experimental::RNTupleWriter; +using RNTupleModel = ROOT::Experimental::RNTupleModel; +using RFieldBase = ROOT::Experimental::Detail::RFieldBase; +using RNTupleDraw = ROOT::Experimental::RNTupleDraw; +using RDrawStorage = ROOT::Experimental::Detail::RDrawStorage; + +/** + * It is hard to test the exact behaviour inside a TCanvas, so this test only checks if no + * exception occurs and the most important member variables hold the correct value. + */ + +namespace { + /** + * An RAII wrapper around an open temporary file on disk. It cleans up the guarded file when the wrapper object + * goes out of scope. + */ +class FileRaii { +private: + std::string fPath; + TFile *file; +public: + FileRaii(const std::string &path) : + fPath(path), + file(TFile::Open(fPath.c_str(), "RECREATE")) + { } + FileRaii(const FileRaii&) = delete; + FileRaii& operator=(const FileRaii&) = delete; + TFile* GetFile() const { return file; } + std::string GetPath() const { return fPath; } + ~FileRaii() { + file->Close(); + std::remove(fPath.c_str()); + } +}; +} // anonymous namespace + +// Should print a message, that nothing was drawn. +TEST(PrintStorageLayout, emptyNTuple) +{ + FileRaii fileGuard("test_printStorageLayout_empty.root"); + std::string_view ntupleName = "emptyFile"; + { + auto model = RNTupleModel::Create(); + auto ntuple = RNTupleWriter::Recreate(std::move(model), ntupleName, fileGuard.GetPath()); + } // flushes content to file + auto reader = RNTupleReader::Open(ntupleName, fileGuard.GetPath()); + auto draw = RNTupleDraw::Open(reader); + draw->Draw(); + + FileRaii fileGuard2("test_printStorageLayout_empty2.root"); + std::string_view ntupleName2 = "emptyFile2"; + { + auto model = RNTupleModel::Create(); + auto intfld = model->MakeField("intfield"); + auto ntuple = RNTupleWriter::Recreate(std::move(model), ntupleName2, fileGuard2.GetPath()); + } // flushes content to file + auto reader2 = RNTupleReader::Open(ntupleName2, fileGuard2.GetPath()); + auto draw2 = RNTupleDraw(reader2); + draw2.Draw(); +} + +TEST(PrintStorageLayout, rootFile) +{ + FileRaii fileGuard("test_printStorageLayout_rootFile.root"); + std::string_view ntupleName = "rootFile"; + { + auto model = RNTupleModel::Create(); + auto intfld = model->MakeField("intField"); + auto doublefld = model->MakeField("doubleField"); + auto ntuple = RNTupleWriter::Recreate(std::move(model), ntupleName, fileGuard.GetPath()); + *intfld = 9; + *doublefld = 3.14; + ntuple->Fill(); + } // flushes content to file + auto model = RNTupleModel::Create(); + auto intptr = model->MakeField("intField"); + auto reader = RNTupleReader::Open(std::move(model), ntupleName, fileGuard.GetPath()); + auto drawStorage = RDrawStorage(reader.get()); + EXPECT_EQ(3ull, drawStorage.GetNFields()); + EXPECT_EQ(2ull, drawStorage.GetNColumns()); + EXPECT_EQ(1ull, drawStorage.GetNClusters()); + EXPECT_EQ(2ull, drawStorage.GetPageBoxSize()); + drawStorage.Draw(); +} + +TEST(PrintStorageLayout, rawFile) +{ + FileRaii fileGuard("test_printStorageLayout_rawFile.ntuple"); + std::string_view ntupleName = "rawFile"; + { + auto model = RNTupleModel::Create(); + auto stringptr = model->MakeField("stringField"); + auto doubleVecptr = model->MakeField>("doubleVectorField"); + auto ntuple = RNTupleWriter::Recreate(std::move(model), ntupleName, fileGuard.GetPath()); + *stringptr = std::string("bus"); + *doubleVecptr = std::vector(2, 2); + ntuple->Fill(); + } // flushes content to file + auto model = RNTupleModel::Create(); + auto stringptr = model->MakeField("stringField"); + auto reader = RNTupleReader::Open(std::move(model), ntupleName, fileGuard.GetPath()); + auto drawStorage = RDrawStorage(reader.get()); + EXPECT_EQ(4ull, drawStorage.GetNFields()); + EXPECT_EQ(4ull, drawStorage.GetNColumns()); + EXPECT_EQ(1ull, drawStorage.GetNClusters()); + EXPECT_EQ(4ull, drawStorage.GetPageBoxSize()); + drawStorage.Draw(); +} + +TEST(PrintStorageLayout, callTwice) +{ + FileRaii fileGuard("test_printStorageLayout_callTwice.root"); + std::string_view ntupleName = "rootFile"; + { + auto model = RNTupleModel::Create(); + auto intfld = model->MakeField>("intarrayField"); + auto vecvecfld = model->MakeField>>("vecvecdoubleField"); + auto ntuple = RNTupleWriter::Recreate(std::move(model), ntupleName, fileGuard.GetPath()); + *intfld = { 1, 2 }; + *vecvecfld = { { 8.2, 8.3 }, { 3.4, 5,6 } }; + ntuple->Fill(); + } // flushes content to file + auto model = RNTupleModel::Create(); + auto intfld = model->MakeField>("intarrayField"); + auto reader = RNTupleReader::Open(std::move(model), ntupleName, fileGuard.GetPath()); + auto drawStorage = RDrawStorage(reader.get()); + drawStorage.Draw(); + drawStorage.Draw(); + EXPECT_EQ(6ull, drawStorage.GetNFields()); + EXPECT_EQ(4ull, drawStorage.GetNColumns()); + EXPECT_EQ(1ull, drawStorage.GetNClusters()); + EXPECT_EQ(4ull, drawStorage.GetPageBoxSize()); +} From 533d425c82de89b92793379c1c9b9f966c4ac9b7 Mon Sep 17 00:00:00 2001 From: lesimon Date: Mon, 25 Nov 2019 20:14:35 +0100 Subject: [PATCH 2/4] make TPad and TH1F from unique_ptr to RawPtr --- tree/ntupledraw/v7/inc/ROOT/RDrawStorage.hxx | 6 ++++-- tree/ntupledraw/v7/src/RDrawStorage.cxx | 20 ++++++++++---------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/tree/ntupledraw/v7/inc/ROOT/RDrawStorage.hxx b/tree/ntupledraw/v7/inc/ROOT/RDrawStorage.hxx index d71c3c92fb31d..663b45cba1133 100644 --- a/tree/ntupledraw/v7/inc/ROOT/RDrawStorage.hxx +++ b/tree/ntupledraw/v7/inc/ROOT/RDrawStorage.hxx @@ -127,8 +127,10 @@ private: std::vector> fPageBoxes; std::vector> fTexts; std::vector> fLines; - std::vector> fPads; - std::vector> fTH1Fs; + // Not Stored as a unique_ptr because ROOT keeps a list of open TPads and may delete fPad before the destructor of this class is called. + TPad* fPad; + // Not Stored as as unique_ptrs in a vector because ROOT keeps a list of open TPads and may delete fPad before the destructor of this class is called. + std::vector fTH1Fs; std::string fNTupleName; std::string fAxisTitle; // for TLatex in RDrawStorage::Draw() std::unique_ptr fHeaderBox; diff --git a/tree/ntupledraw/v7/src/RDrawStorage.cxx b/tree/ntupledraw/v7/src/RDrawStorage.cxx index 20d147642505f..cfb3009565c52 100644 --- a/tree/ntupledraw/v7/src/RDrawStorage.cxx +++ b/tree/ntupledraw/v7/src/RDrawStorage.cxx @@ -458,17 +458,17 @@ void ROOT::Experimental::Detail::RDrawStorage::Draw() // 3. Create a TPad in the canvas so that when zooming only the boxes and axis get zoomed constexpr double marginlength = 0.03; std::string uniquePadName = "RDrawStoragePad" + std::to_string(uniqueId); - fPads.emplace_back(std::make_unique(uniquePadName.c_str(), "", marginlength, 0.55, 1 - marginlength, 0.87)); - fPads.back()->SetTopMargin(0.2); - fPads.back()->SetBottomMargin(0.2); - fPads.back()->SetLeftMargin(0.01); - fPads.back()->SetRightMargin(0.01); - fPads.back()->Draw(); - fPads.back()->cd(); + fPad = new TPad(uniquePadName.c_str(), "", marginlength, 0.55, 1 - marginlength, 0.87); + fPad->SetTopMargin(0.2); + fPad->SetBottomMargin(0.2); + fPad->SetLeftMargin(0.01); + fPad->SetRightMargin(0.01); + fPad->Draw(); + fPad->cd(); // 4. Draw an empty histogram without a y-axis for zooming std::string uniqueTH1FName = "RDrawStorageTH1F" + std::to_string(uniqueId); - fTH1Fs.emplace_back(std::make_unique(uniqueTH1FName.c_str(), "", 500, 0, (double)fTotalNumBytes / fScalingFactorOfAxis)); + fTH1Fs.emplace_back(new TH1F(uniqueTH1FName.c_str(), "", 500, 0, (double)fTotalNumBytes / fScalingFactorOfAxis)); fTH1Fs.back()->SetMaximum(1); fTH1Fs.back()->SetMinimum(0); fTH1Fs.back()->GetYaxis()->SetTickLength(0); @@ -483,7 +483,7 @@ void ROOT::Experimental::Detail::RDrawStorage::Draw() b->Draw(); } fFooterBox->Draw(); - fPads.back()->AddExec("ShowPageDetails", "ROOT::Experimental::Detail::RDrawStorage::RPageBoxClicked()"); + fPad->AddExec("ShowPageDetails", "ROOT::Experimental::Detail::RDrawStorage::RPageBoxClicked()"); // 6. Draw clusterAxis // fTexts.at(0) points to Title so skip @@ -493,7 +493,7 @@ void ROOT::Experimental::Detail::RDrawStorage::Draw() for (const auto &l : fLines) { l->Draw(); } - fPads.back()->Update(); + fPad->Update(); // 7. Return to canvas, draw title, legend and description of x-axis fCanvasPtrs.back()->cd(); From d8f769d00c3126a7c2577552c60bfa5e69dd3c36 Mon Sep 17 00:00:00 2001 From: lesimon Date: Tue, 26 Nov 2019 08:37:22 +0100 Subject: [PATCH 3/4] cosmetics --- tree/ntupledraw/v7/inc/ROOT/RDrawStorage.hxx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tree/ntupledraw/v7/inc/ROOT/RDrawStorage.hxx b/tree/ntupledraw/v7/inc/ROOT/RDrawStorage.hxx index 663b45cba1133..af43a48d0bd34 100644 --- a/tree/ntupledraw/v7/inc/ROOT/RDrawStorage.hxx +++ b/tree/ntupledraw/v7/inc/ROOT/RDrawStorage.hxx @@ -127,9 +127,9 @@ private: std::vector> fPageBoxes; std::vector> fTexts; std::vector> fLines; - // Not Stored as a unique_ptr because ROOT keeps a list of open TPads and may delete fPad before the destructor of this class is called. - TPad* fPad; - // Not Stored as as unique_ptrs in a vector because ROOT keeps a list of open TPads and may delete fPad before the destructor of this class is called. + // Not Stored as a unique_ptr because ROOT will sometimes delete fPad before the destructor of this class is called. + TPad *fPad; + // Not Stored as as unique_ptrs in a vector because ROOT will sometimes delete fTH1Fs before the destructor of this class is called. std::vector fTH1Fs; std::string fNTupleName; std::string fAxisTitle; // for TLatex in RDrawStorage::Draw() @@ -141,7 +141,7 @@ private: public: // holds all generated cavas pointers in case they need to be manually destructed in the future due to changes in // ROOT. - std::vector fCanvasPtrs; + std::vector fCanvasPtrs; RDrawStorage(RNTupleReader *reader); /// holds all created RDrawStorage instances until the lifetime static std::vector fgDrawStorageVec; From 17224399587d75c3a0cffde8d958f7df53e0d676 Mon Sep 17 00:00:00 2001 From: lesimon Date: Fri, 29 Nov 2019 12:11:29 +0100 Subject: [PATCH 4/4] Implement requested changes --- tree/ntuple/v7/inc/ROOT/RColumnModel.hxx | 48 +++++++++- tree/ntupledraw/v7/inc/ROOT/RDrawStorage.hxx | 27 +++--- tree/ntupledraw/v7/src/RDrawStorage.cxx | 92 +++----------------- tree/ntupledraw/v7/test/ntupledraw.cxx | 7 +- 4 files changed, 80 insertions(+), 94 deletions(-) diff --git a/tree/ntuple/v7/inc/ROOT/RColumnModel.hxx b/tree/ntuple/v7/inc/ROOT/RColumnModel.hxx index c5673bba4bc86..d9b9c3eceea22 100644 --- a/tree/ntuple/v7/inc/ROOT/RColumnModel.hxx +++ b/tree/ntuple/v7/inc/ROOT/RColumnModel.hxx @@ -16,8 +16,10 @@ #ifndef ROOT7_RColumnModel #define ROOT7_RColumnModel +#include #include +#include #include namespace ROOT { @@ -43,11 +45,53 @@ enum class EColumnType { kBit, kReal64, kReal32, + // kReal24, to uncomment after implementing custom-sized float kReal16, kReal8, kInt64, kInt32, kInt16, + // kCustomDouble, to uncomment after implementing custom-sized float + // kCustomFloat, to uncomment after implementing custom-sized float +}; + +// clang-format off +/** +\class ROOT::Experimental::RColumnTypeIdentifier +\ingroup NTuple +\brief Holds static arrays with EColumnType MetaData + +Contains static arrays to obtain information about a specific columnType. +*/ +// clang-format on +class RColumnTypeIdentifier { +public: + static constexpr std::array fColumnTypeNames{ + "Unknown", "Index", "Switch", "Byte", "Bit", "Real64", "Real32", /*"Real24", */ "Real16", + "Real8", "Int64", "Int32", "Int16" /*, "CustomDouble", "CustomFloat"*/}; + static constexpr std::array fColumnBitSizeOnDisk{ + 0, + sizeof(ClusterSize_t) * 8, + sizeof(ROOT::Experimental::RColumnSwitch) * 8, + sizeof(char) * 8, + sizeof(bool) * 8, + sizeof(double) * 8, + sizeof(float) * 8,/*, 24*/ + 16, + 8, + 64, + 32, + 16 /*, sizeof(double)*8, sizeof(float)*8*/}; + static const char *GetColumnTypeNames(std::uint32_t index) + { + assert(index < fColumnTypeNames.size()); + return fColumnTypeNames[index]; + } + static ClusterSize_t::ValueType GetColumnBitSizeOnDisk(std::uint32_t index) + { + assert(index < fColumnBitSizeOnDisk.size()); + return fColumnBitSizeOnDisk[index]; + } }; // clang-format off @@ -69,9 +113,7 @@ public: EColumnType GetType() const { return fType; } bool GetIsSorted() const { return fIsSorted; } - bool operator ==(const RColumnModel &other) const { - return (fType == other.fType) && (fIsSorted == other.fIsSorted); - } + bool operator==(const RColumnModel &other) const { return (fType == other.fType) && (fIsSorted == other.fIsSorted); } }; } // namespace Experimental diff --git a/tree/ntupledraw/v7/inc/ROOT/RDrawStorage.hxx b/tree/ntupledraw/v7/inc/ROOT/RDrawStorage.hxx index af43a48d0bd34..919ee4b1524eb 100644 --- a/tree/ntupledraw/v7/inc/ROOT/RDrawStorage.hxx +++ b/tree/ntupledraw/v7/inc/ROOT/RDrawStorage.hxx @@ -1,6 +1,6 @@ /// \file ROOT/RDrawStorage.hxx /// \ingroup NTupleDraw ROOT7 -/// \author Simon Leisibach +/// \author Simon Leisibach /// \date 2019-11-07 /// \warning This is part of the ROOT 7 prototype! It will change without notice. It might trigger earthquakes. Feedback /// is welcome! @@ -42,7 +42,8 @@ class RDrawStorage; \ingroup NTupleDraw \brief A TBox which contains metadata information of a RNTuple -A RMetaDataBox is drawn on the TCanvas showing the RNTuple storage layout and represents some metadata (header or footer) in the RNTuple. It also holds some data of the metadata it represents, like its byte size. +A RMetaDataBox is drawn on the TCanvas showing the RNTuple storage layout and represents some metadata (header or +footer) in the RNTuple. It also holds some data of the metadata it represents, like its byte size. */ // clang-format on class RMetaDataBox : public TBox { @@ -67,7 +68,8 @@ public: \ingroup NTupleDraw \brief A TBox which represents a RPage -A RPageBox is drawn on the TCanvas showing the RNTuple storage layout and represents a RPage in the RNTuple. It also holds various data of a RPage, which allows the user to dump/inspect the RPageBox to obtain information about the RPage. +A RPageBox is drawn on the TCanvas showing the RNTuple storage layout and represents a RPage in the RNTuple. It also +holds various data of a RPage, which allows the user to dump/inspect the RPageBox to obtain information about the RPage. */ // clang-format on class RPageBox : public TBox { @@ -110,7 +112,8 @@ public: \ingroup NTupleDraw \brief Main class for drawing the storage layout of a RNTuple -This class coordinates the drawing process of the storage layout of a RNTuple. It also holds all generated unique pointers with a static member variable until ROOT is terminated, in order for the drawing to persist. +This class coordinates the drawing process of the storage layout of a RNTuple. It also holds all generated unique +pointers with a static member variable until ROOT is terminated, in order for the drawing to persist. */ // clang-format on class RDrawStorage { @@ -129,8 +132,9 @@ private: std::vector> fLines; // Not Stored as a unique_ptr because ROOT will sometimes delete fPad before the destructor of this class is called. TPad *fPad; - // Not Stored as as unique_ptrs in a vector because ROOT will sometimes delete fTH1Fs before the destructor of this class is called. - std::vector fTH1Fs; + // Not Stored as as unique_ptrs in a vector because ROOT will sometimes delete fTH1Fs before the destructor of this + // class is called. + std::vector fTH1Fs; std::string fNTupleName; std::string fAxisTitle; // for TLatex in RDrawStorage::Draw() std::unique_ptr fHeaderBox; @@ -141,9 +145,10 @@ private: public: // holds all generated cavas pointers in case they need to be manually destructed in the future due to changes in // ROOT. - std::vector fCanvasPtrs; + std::vector fCanvasPtrs; RDrawStorage(RNTupleReader *reader); - /// holds all created RDrawStorage instances until the lifetime + /// holds all created RDrawStorage instances until the ROOT program is terminated. Deleting a RDrawStorage earlier + /// could cause objects drawn on the canvas to get deleted along with it. static std::vector fgDrawStorageVec; static std::int32_t GetColourFromFieldId(DescriptorId_t fieldId); std::size_t GetPageBoxSize() const { return fPageBoxes.size(); } @@ -163,16 +168,18 @@ public: \ingroup NTupleDraw \brief User interface for drawing the structure of an ntuple -This class acts as a user interface like RNTupleWriter and RNTupleReader. It acts as a delegeator to RDrawStorage instead of doing the drawing job itself. This way the lifetime of the data displayed on the canvas is tied to the termination of the ROOT program and not to the destruction of a RNTupleDraw or RNTupleReader instance. +This class acts as a user interface like RNTupleWriter and RNTupleReader. It acts as a delegeator to RDrawStorage +instead of doing the drawing job itself. This way the lifetime of the data displayed on the canvas is tied to the +termination of the ROOT program and not to the destruction of a RNTupleDraw or RNTupleReader instance. */ // clang-format on class RNTupleDraw { private: Detail::RDrawStorage *fStorage; bool fEmpty = false; + public: RNTupleDraw(const std::unique_ptr &reader); - static std::unique_ptr Open(const std::unique_ptr &reader); void Draw(); }; } // namespace Experimental diff --git a/tree/ntupledraw/v7/src/RDrawStorage.cxx b/tree/ntupledraw/v7/src/RDrawStorage.cxx index cfb3009565c52..dc07b8a55a135 100644 --- a/tree/ntupledraw/v7/src/RDrawStorage.cxx +++ b/tree/ntupledraw/v7/src/RDrawStorage.cxx @@ -1,6 +1,6 @@ /// \file RDrawStorage.cxx /// \ingroup NTupleDraw ROOT7 -/// \author Simon Leisibach +/// \author Simon Leisibach /// \date 2019-11-07 /// \warning This is part of the ROOT 7 prototype! It will change without notice. It might trigger earthquakes. Feedback /// is welcome! @@ -33,6 +33,7 @@ #include #include +#include #include #include #include @@ -83,9 +84,9 @@ void ROOT::Experimental::Detail::RMetaDataBox::Inspect() const ClassImp(ROOT::Experimental::Detail::RMetaDataBox) - // ------------------------------ RPageBox ---------------------------------- +// ------------------------------ RPageBox ---------------------------------- - void ROOT::Experimental::Detail::RPageBox::Dump() const +void ROOT::Experimental::Detail::RPageBox::Dump() const { std::cout << " ==> Dumping Page information:\n\n"; std::cout << "Page Id: \t\t" << fPageBoxId << " / " << fParent->GetPageBoxSize() << std::endl; @@ -177,9 +178,12 @@ void ROOT::Experimental::Detail::RPageBox::Inspect() const ClassImp(ROOT::Experimental::Detail::RPageBox) - // ------------------------------ RDrawStorage ------------------------------ +// ------------------------------ RDrawStorage ------------------------------ - ROOT::Experimental::Detail::RPageBox::RPageBox(double x1, double y1, double x2, double y2, std::string fieldName, +constexpr std::array ROOT::Experimental::RColumnTypeIdentifier::fColumnTypeNames; +constexpr std::array ROOT::Experimental::RColumnTypeIdentifier::fColumnBitSizeOnDisk; + +ROOT::Experimental::Detail::RPageBox::RPageBox(double x1, double y1, double x2, double y2, std::string fieldName, std::string fieldType, DescriptorId_t fieldId, DescriptorId_t columnId, DescriptorId_t clusterId, EColumnType columnType, ClusterSize_t::ValueType nElements, @@ -190,70 +194,8 @@ ClassImp(ROOT::Experimental::Detail::RPageBox) fClusterId{clusterId}, fNElements{nElements}, fGlobalRangeStart{globalRangeStart}, fClusterRangeStart{clusterRangeStart}, fLocator{locator}, fParent{parent}, fPageBoxId{pageBoxId} { - switch (columnType) { - case EColumnType::kIndex: - fColumnType = "Index"; - fElementSizeOnDisk = sizeof(ClusterSize_t) * 8; - break; - case EColumnType::kSwitch: - fColumnType = "Switch"; - fElementSizeOnDisk = sizeof(ROOT::Experimental::RColumnSwitch) * 8; - break; - case EColumnType::kByte: - fColumnType = "Byte"; - fElementSizeOnDisk = sizeof(char) * 8; - break; - case EColumnType::kBit: - fColumnType = "Bit"; - fElementSizeOnDisk = sizeof(bool) * 8; - break; - case EColumnType::kReal64: - fColumnType = "Real64"; - fElementSizeOnDisk = sizeof(double) * 8; - break; - case EColumnType::kReal32: - fColumnType = "Real32"; - fElementSizeOnDisk = sizeof(float) * 8; - break; - // Uncomment after implementing custom-sized float-packing. - /*case EColumnType::kReal24: - fColumnType = "Real24"; - fElementSizeOnDisk = 24; - break; - case EColumnType::kCustomDouble: - fColumnType = "CustomDouble"; - fElementSizeOnDisk = sizeof(double)*8; - break; - case EColumnType::kCustomFloat: - fColumnType = "CustomFloat"; - fElementSizeOnDisk = sizeof(float)*8; - break;*/ - case EColumnType::kReal16: - fColumnType = "Real16"; - fElementSizeOnDisk = 16; - break; - case EColumnType::kReal8: - fColumnType = "Real8"; - fElementSizeOnDisk = 8; - break; - case EColumnType::kInt64: - fColumnType = "Int64"; - fElementSizeOnDisk = 64; - break; - case EColumnType::kInt32: - fColumnType = "Int32"; - fElementSizeOnDisk = 32; - break; - case EColumnType::kInt16: - fColumnType = "Int16"; - fElementSizeOnDisk = 16; - break; - case EColumnType::kUnknown: - fColumnType = "kUnknown"; - fElementSizeOnDisk = -1; - break; - default: assert(false); - } + fColumnType = std::string{RColumnTypeIdentifier::GetColumnTypeNames(static_cast(columnType))}; + fElementSizeOnDisk = RColumnTypeIdentifier::GetColumnBitSizeOnDisk(static_cast(columnType)); } ROOT::Experimental::Detail::RDrawStorage::RDrawStorage(ROOT::Experimental::RNTupleReader *reader) @@ -451,7 +393,8 @@ void ROOT::Experimental::Detail::RDrawStorage::Draw() // 2. Create a new canvas static std::int32_t uniqueId = 0; - // Trying to delete multiple canvases with the same name leads to an error or when two canvases have the same name, only 1 may get deleted, causing a memory leak. + // Trying to delete multiple canvases with the same name leads to an error or when two canvases have the same name, + // only 1 may get deleted, causing a memory leak. std::string uniqueCanvasName = "RDrawStorage" + std::to_string(++uniqueId); fCanvasPtrs.emplace_back(new TCanvas(uniqueCanvasName.c_str(), fNTupleName.c_str(), 1000, 300)); @@ -581,15 +524,6 @@ ROOT::Experimental::RNTupleDraw::RNTupleDraw(const std::unique_ptr -ROOT::Experimental::RNTupleDraw::Open(const std::unique_ptr &reader) -{ - if (reader == nullptr) { - std::cout << "The RNTupleReader is invalid, a nullptr was returned." << std::endl; - return nullptr; - } - return std::make_unique(reader); -} void ROOT::Experimental::RNTupleDraw::Draw() { diff --git a/tree/ntupledraw/v7/test/ntupledraw.cxx b/tree/ntupledraw/v7/test/ntupledraw.cxx index b2dfafc686c62..b264cda39c026 100644 --- a/tree/ntupledraw/v7/test/ntupledraw.cxx +++ b/tree/ntupledraw/v7/test/ntupledraw.cxx @@ -57,8 +57,8 @@ TEST(PrintStorageLayout, emptyNTuple) auto ntuple = RNTupleWriter::Recreate(std::move(model), ntupleName, fileGuard.GetPath()); } // flushes content to file auto reader = RNTupleReader::Open(ntupleName, fileGuard.GetPath()); - auto draw = RNTupleDraw::Open(reader); - draw->Draw(); + auto draw = RNTupleDraw(reader); + draw.Draw(); FileRaii fileGuard2("test_printStorageLayout_empty2.root"); std::string_view ntupleName2 = "emptyFile2"; @@ -118,6 +118,9 @@ TEST(PrintStorageLayout, rawFile) EXPECT_EQ(1ull, drawStorage.GetNClusters()); EXPECT_EQ(4ull, drawStorage.GetPageBoxSize()); drawStorage.Draw(); + // To check if RNTupleReader is not dead after drawing + std::stringstream os; + reader->PrintInfo(ROOT::Experimental::ENTupleInfo::kSummary, os); } TEST(PrintStorageLayout, callTwice)