From 476441187bb4d26bac85468b19fb0e4cda8ba33e Mon Sep 17 00:00:00 2001
From: "s.zharko@gsi.de" <s.zharko@gsi.de>
Date: Wed, 12 Jun 2024 03:34:56 +0200
Subject: [PATCH] QA online: possibility to add the TS index axis

---
 algo/qa/Histogram.h                   | 235 ++++++++++++++++++--------
 algo/qa/HistogramContainer.cxx        |  19 +--
 algo/qa/HistogramContainer.h          |   2 +
 algo/qa/QaData.cxx                    |  36 ++--
 core/qa/report/CbmQaReportElement.h   |   2 +-
 services/histserv/app/Application.cxx | 115 +++++++------
 services/histserv/app/Application.h   |  92 +++++-----
 7 files changed, 310 insertions(+), 191 deletions(-)

diff --git a/algo/qa/Histogram.h b/algo/qa/Histogram.h
index d05b17bc38..c3179618c3 100644
--- a/algo/qa/Histogram.h
+++ b/algo/qa/Histogram.h
@@ -4,7 +4,7 @@
 
 /// \file   Histogram.h
 /// \date   27.02.2024
-/// \brief  Interface to 1D histogram
+/// \brief  ROOT-free implementation of a histogram
 /// \author Sergei Zharko <s.zharko@gsi.de>
 
 #pragma once
@@ -27,6 +27,8 @@
 #include <tuple>
 #include <type_traits>
 
+#include <fmt/format.h>
+
 namespace cbm::algo::qa
 {
   namespace bh = boost::histogram;
@@ -48,6 +50,79 @@ namespace cbm::algo::qa
     Z
   };
 
+  /// \enum  EHistFlag
+  /// \brief Histogram control flags (bit masks)
+  enum class EHistFlag : uint8_t
+  {
+    StoreVsTsId    = 0b00000001,  ///< Store the histogram with the additional timeslice evolution axis 
+    OmitIntegrated = 0b00000010   ///< Omits storing integrated histogram 
+  };
+
+  /// \class  HistogramMetadata
+  /// \brief  Metadata of the histogram
+  /// \note   To be sent with a configuration to the histogram server
+  class HistogramMetadata {
+   public:
+    using Flags_t = std::underlying_type_t<EHistFlag>;
+
+    /// \brief Default constructor
+    HistogramMetadata() = default;
+
+    /// \brief Constructor from the metadata string representation
+    /// \param msg  Metadata string representation
+    explicit HistogramMetadata(const std::string& msg)
+    {
+      if (!msg.empty()) {
+        fFlags = std::stoi(msg, nullptr, 16);
+      }
+    }
+
+    /// \brief Destructor
+    ~HistogramMetadata() = default;
+
+    /// \brief Get flag
+    /// \param key  Flag key from the EHistFlag enumeration
+    bool GetFlag(EHistFlag key) const { return static_cast<bool>(fFlags & static_cast<Flags_t>(key)); }
+
+    /// \brief Get flag
+    /// \param key   Flag key from the EHistFlag enumeration
+    /// \param flag  Flag value
+    void SetFlag(EHistFlag key, bool flag = true)
+    {
+      flag ? (fFlags |= static_cast<Flags_t>(key)) : (fFlags &= ~static_cast<Flags_t>(key));
+    }
+
+    /// \brief Converts the metadata to a string
+    ///
+    /// Current implementation of the metadata string: <flags>
+    /// Future implementation with additional fields: <flags>;<...>;<...>;...;<...>
+    std::string ToString() const { return fmt::format("{0:02x}", fFlags); }
+
+    /// \brief  Separates a name and metadata of histogram
+    /// \return std::pair<std::string, std::string>: (name, metadata)
+    static std::pair<std::string, std::string> SeparateNameAndMetadata(const std::string& msg)
+    {
+      size_t pos = msg.find_last_of('!');
+      if (pos != msg.npos) {
+        return std::make_pair(msg.substr(0, pos), msg.substr(pos + 1));
+      }
+      else {
+        return std::make_pair(msg, "");
+      }
+    }
+
+   private:
+    friend class boost::serialization::access;
+    template<class Archive>
+    /// \brief Serialization rule
+    void serialize(Archive& ar, const unsigned int /*version*/)
+    {
+      ar& fFlags;
+    }
+
+    Flags_t fFlags{};  ///< Flags collection for the histogram
+  };
+
   /// \class  TotalSums1D
   /// \brief  Storage for total sums of weights, squared weights, weights over x, weights over squared x
   class TotalSums1D {
@@ -64,18 +139,6 @@ namespace cbm::algo::qa
     /// \brief Gets total sum of weight over squared x products
     double GetTotSumWX2() const { return fTotSumWX2; }
 
-   private:
-    /// \brief Serialization rule
-    friend class boost::serialization::access;
-    template<class Archive>
-    void serialize(Archive& ar, const unsigned int /*version*/)
-    {
-      ar& fTotSumW;
-      ar& fTotSumW2;
-      ar& fTotSumWX;
-      ar& fTotSumWX2;
-    }
-
    protected:
     /// \brief Updates the sums
     /// \param x  X value
@@ -101,6 +164,18 @@ namespace cbm::algo::qa
     double fTotSumW2  = 0.;  ///< Total sum (over all bins) of squared weights
     double fTotSumWX  = 0.;  ///< Total sum (over all bins) of weight over x products
     double fTotSumWX2 = 0.;  ///< Total sum (over all bins) of weight over square x products
+
+   private:
+    /// \brief Serialization rule
+    friend class boost::serialization::access;
+    template<class Archive>
+    void serialize(Archive& ar, const unsigned int /*version*/)
+    {
+      ar& fTotSumW;
+      ar& fTotSumW2;
+      ar& fTotSumWX;
+      ar& fTotSumWX2;
+    }
   };
 
   /// \class  TotalSums2D
@@ -116,18 +191,6 @@ namespace cbm::algo::qa
     /// \brief Gets total sum of weight over squared y products
     double GetTotSumWY2() const { return fTotSumWY2; }
 
-   private:
-    /// \brief Serialization rule
-    friend class boost::serialization::access;
-    template<class Archive>
-    void serialize(Archive& ar, const unsigned int /*version*/)
-    {
-      ar& boost::serialization::base_object<TotalSums1D>(*this);
-      ar& fTotSumWXY;
-      ar& fTotSumWY;
-      ar& fTotSumWY2;
-    }
-
    protected:
     /// \brief Resets the sums
     void Reset()
@@ -153,6 +216,18 @@ namespace cbm::algo::qa
     double fTotSumWY  = 0.;  ///< Total sum (over all bins) of weight over y products
     double fTotSumWXY = 0.;  ///< Total sum (over all bins) of weight over square x products
     double fTotSumWY2 = 0.;  ///< Total sum (over all bins) of weight over x over y products
+
+   private:
+    /// \brief Serialization rule
+    friend class boost::serialization::access;
+    template<class Archive>
+    void serialize(Archive& ar, const unsigned int /*version*/)
+    {
+      ar& boost::serialization::base_object<TotalSums1D>(*this);
+      ar& fTotSumWXY;
+      ar& fTotSumWY;
+      ar& fTotSumWY2;
+    }
   };
 
 
@@ -171,16 +246,21 @@ namespace cbm::algo::qa
     /// \brief Destructor
     ~Histogram() = default;
 
-    /// \brief Gets number of entries
-    // TODO: Gives different results, if weights are not 1 (investigate!)
-    double GetEntries() const
+    /// \brief  Gets range lower bound of the selected axis
+    /// \tparam AxisType  An axis
+    template<EAxis A, unsigned IA = static_cast<unsigned>(A), std::enable_if_t<(IA < Rank), bool> = true>
+    double GetAxisMin() const
     {
-      // return bh::algorithm::sum(fHistogram); // -> effective entries (but different to the ROOT ones: if w != 1)
-      return fEntries;
+      return fHistogram.template axis<IA>().value(0.);
     }
 
-    /// \brief Gets name
-    const std::string& GetName() const { return fName; }
+    /// \brief  Gets range upper bound of the selected axis
+    /// \tparam AxisType  An axis
+    template<EAxis A, unsigned IA = static_cast<unsigned>(A), std::enable_if_t<(IA < Rank), bool> = true>
+    double GetAxisMax() const
+    {
+      return fHistogram.template axis<IA>().value(static_cast<double>(GetAxisNbins<A>()));
+    }
 
     /// \brief  Gets number of bins in axis
     /// \tparam AxisT  An axis
@@ -191,21 +271,26 @@ namespace cbm::algo::qa
       return fHistogram.template axis<IA>().size();
     }
 
-    /// \brief  Gets range lower bound of the selected axis
-    /// \tparam AxisType  An axis
-    template<EAxis A, unsigned IA = static_cast<unsigned>(A), std::enable_if_t<(IA < Rank), bool> = true>
-    double GetAxisMin() const
+    /// \brief Gets number of entries
+    // TODO: Gives different results, if weights are not 1 (investigate!)
+    double GetEntries() const
     {
-      return fHistogram.template axis<IA>().value(0.);
+      // return bh::algorithm::sum(fHistogram); // -> effective entries (but different to the ROOT ones: if w != 1)
+      return fEntries;
     }
 
-    /// \brief  Gets range upper bound of the selected axis
-    /// \tparam AxisType  An axis
-    template<EAxis A, unsigned IA = static_cast<unsigned>(A), std::enable_if_t<(IA < Rank), bool> = true>
-    double GetAxisMax() const
-    {
-      return fHistogram.template axis<IA>().value(static_cast<double>(GetAxisNbins<A>()));
-    }
+    /// \brief Get flag
+    /// \param key  Flag key from the EHistFlag enumeration
+    bool GetFlag(EHistFlag key) const { return fMetadata.GetFlag(key); }
+
+    /// \brief Gets name
+    const std::string& GetName() const { return fName; }
+
+    /// \brief Gets metadata instance
+    const HistogramMetadata& GetMetadata() const { return fMetadata; }
+
+    /// \brief Gets metadata string
+    std::string GetMetadataString() const { return fMetadata.ToString(); }
 
     /// \brief Gets number of bins for x axis
     uint32_t GetNbinsX() const { return GetAxisNbins<EAxis::X>(); }
@@ -260,6 +345,11 @@ namespace cbm::algo::qa
       fEntries = 0;
     }
 
+    /// \brief Get flag
+    /// \param key   Flag key from the EHistFlag enumeration
+    /// \param flag  Flag value
+    void SetFlag(EHistFlag key, bool flag = true) { fMetadata.SetFlag(key, flag); }
+
     /// \brief Sets name
     /// \param name  Histogram name
     void SetName(const std::string& name) { fName = name; }
@@ -276,23 +366,11 @@ namespace cbm::algo::qa
     //  msg <<
     //}
 
-   private:
-    friend class boost::serialization::access;
-    template<class Archive>
-    /// \brief Serialization rule
-    void serialize(Archive& ar, const unsigned int /*version*/)
-    {
-      ar& boost::serialization::base_object<TotalSums>(*this);
-      ar& fHistogram;
-      ar& fName;
-      ar& fTitle;
-      ar& fEntries;
-    }
-
    protected:
     /// \brief Default constructor
     Histogram() = default;
 
+    /// \brief Copy constructor
     explicit Histogram(const Histogram<Axes, Storage, TotalSums>& h) = default;
 
     //explicit Histogram(const Histogram<Axes, Storage>& h)
@@ -312,6 +390,7 @@ namespace cbm::algo::qa
       , fName(name)
       , fTitle(title)
       , fEntries(0)
+      , fMetadata(HistogramMetadata())
     {
     }
 
@@ -320,10 +399,26 @@ namespace cbm::algo::qa
     /// \return Bin in boost histogram
     inline static int GetBinBH(uint32_t iBin) { return (iBin > 0) ? iBin - 1 : -1; }
 
-    Hist_t fHistogram;
-    std::string fName  = "";
-    std::string fTitle = "";
-    int fEntries       = 0;  ///< Number of entries
+    Hist_t fHistogram;            ///< Underlying boost histogram
+    std::string fName  = "";      ///< Name of the histogram
+    std::string fTitle = "";      ///< Title of the histogram
+    int fEntries       = 0;       ///< Number of histogram entries
+    HistogramMetadata fMetadata;  ///< Meta-data for histogram
+
+
+   private:
+    friend class boost::serialization::access;
+    template<class Archive>
+    /// \brief Serialization rule
+    void serialize(Archive& ar, const unsigned int /*version*/)
+    {
+      ar& boost::serialization::base_object<TotalSums>(*this);
+      ar& fHistogram;
+      ar& fName;
+      ar& fTitle;
+      ar& fEntries;
+      ar& fMetadata;
+    }
   };
 
   using BaseH1D    = Histogram<Axes1D_t, HistStorage_t, TotalSums1D>;
@@ -378,6 +473,10 @@ namespace cbm::algo::qa
       return iBin;
     }
 
+    /// \brief Gets underlying bin accumulator
+    /// \param iBin  Bin index
+    auto GetBinAccumulator(uint32_t iBin) const { return fHistogram.at(Histogram::GetBinBH(iBin)); }
+
     /// \brief Gets bin content
     /// \param iBin  Bin index
     double GetBinContent(uint32_t iBin) const { return fHistogram.at(Histogram::GetBinBH(iBin)).value(); }
@@ -386,10 +485,6 @@ namespace cbm::algo::qa
     /// \param iBin  Bin index
     double GetBinError(uint32_t iBin) const { return std::sqrt(fHistogram.at(Histogram::GetBinBH(iBin)).variance()); }
 
-    /// \brief Gets underlying bin accumulator
-    /// \param iBin  Bin index
-    auto GetBinAccumulator(uint32_t iBin) const { return fHistogram.at(Histogram::GetBinBH(iBin)); }
-
    private:
     /// \brief Serialization function
     friend class boost::serialization::access;
@@ -493,7 +588,7 @@ namespace cbm::algo::qa
     }
   };
 
-  // NOTE: Boost::histogram by default uses a different error calculation approach as ROOT TProfile. TODO: investigate
+  // TODO: Boost::histogram by default uses a different error calculation approach as ROOT TProfile. TODO: investigate
 
   class Prof1D : public BaseProf1D {
    public:
@@ -587,8 +682,8 @@ namespace cbm::algo::qa
       ar& fYmax;
     }
 
-    double fYmin = 0.;
-    double fYmax = 0.;
+    double fYmin = 0.;  ///< Lower bound of the profile y-axis
+    double fYmax = 0.;  ///< Upper bound of the profile y-axis
   };
 
   class Prof2D : public BaseProf2D {
@@ -710,8 +805,8 @@ namespace cbm::algo::qa
       ar& fZmax;
     }
 
-    double fZmin = 0.;
-    double fZmax = 0.;
+    double fZmin = 0.;  ///< Lower bound of the profile z-axis
+    double fZmax = 0.;  ///< Upper bound of the profile z-axis
   };
 
 }  // namespace cbm::algo::qa
diff --git a/algo/qa/HistogramContainer.cxx b/algo/qa/HistogramContainer.cxx
index 8db32a09a1..b70cb5f371 100644
--- a/algo/qa/HistogramContainer.cxx
+++ b/algo/qa/HistogramContainer.cxx
@@ -9,22 +9,17 @@
 
 #include "HistogramContainer.h"
 
+#include <algorithm>
+
 using cbm::algo::qa::HistogramContainer;
 
 // ---------------------------------------------------------------------------------------------------------------------
 //
 void HistogramContainer::Reset()
 {
-  for (auto& h : fvH1) {
-    h.Reset();
-  }
-  for (auto& h : fvH2) {
-    h.Reset();
-  }
-  for (auto& h : fvP1) {
-    h.Reset();
-  }
-  for (auto& h : fvP2) {
-    h.Reset();
-  }
+  constexpr auto ResetHistograms = [&](auto& h) { h.Reset(); };
+  std::for_each(fvH1.begin(), fvH1.end(), ResetHistograms);
+  std::for_each(fvH2.begin(), fvH2.end(), ResetHistograms);
+  std::for_each(fvP1.begin(), fvP1.end(), ResetHistograms);
+  std::for_each(fvP2.begin(), fvP2.end(), ResetHistograms);
 }
diff --git a/algo/qa/HistogramContainer.h b/algo/qa/HistogramContainer.h
index eb8630a6ca..3816c0bc88 100644
--- a/algo/qa/HistogramContainer.h
+++ b/algo/qa/HistogramContainer.h
@@ -24,6 +24,7 @@ namespace cbm::algo::qa
     std::forward_list<qa::H2D> fvH2    = {};  ///< List of 2D-histograms
     std::forward_list<qa::Prof1D> fvP1 = {};  ///< List of 1D-profiles
     std::forward_list<qa::Prof2D> fvP2 = {};  ///< List of 2D-profiles
+    int fTimeSliceId                   = 0;   ///< Index of the time-slice
 
     /// \brief Resets the histograms
     void Reset();
@@ -37,6 +38,7 @@ namespace cbm::algo::qa
       ar& fvH2;
       ar& fvP1;
       ar& fvP2;
+      ar& fTimeSliceId;
     }
   };
 }  // namespace cbm::algo::qa
diff --git a/algo/qa/QaData.cxx b/algo/qa/QaData.cxx
index 7ab94afa0f..81c53f5e8e 100644
--- a/algo/qa/QaData.cxx
+++ b/algo/qa/QaData.cxx
@@ -9,6 +9,8 @@
 
 #include "QaData.h"
 
+#include <algorithm>
+
 using cbm::algo::qa::Data;
 
 // ---------------------------------------------------------------------------------------------------------------------
@@ -25,18 +27,16 @@ void Data::Init(std::shared_ptr<HistogramSender> histSender)
     nHistograms += std::distance(fHistograms.fvP1.begin(), fHistograms.fvP1.end());
     nHistograms += std::distance(fHistograms.fvP2.begin(), fHistograms.fvP2.end());
     vHistCfgs.reserve(nHistograms);
-    for (const auto& hist : fHistograms.fvH1) {
-      vHistCfgs.emplace_back(hist.GetName(), fsName);
-    }
-    for (const auto& hist : fHistograms.fvH2) {
-      vHistCfgs.emplace_back(hist.GetName(), fsName);
-    }
-    for (const auto& hist : fHistograms.fvP1) {
-      vHistCfgs.emplace_back(hist.GetName(), fsName);
-    }
-    for (const auto& hist : fHistograms.fvP2) {
-      vHistCfgs.emplace_back(hist.GetName(), fsName);
-    }
+
+    auto RegHist = [&](const auto& h) {
+      //vHistCfgs.emplace_back(h.GetName() + "!" + h.GetMetadataString(), fsName);
+      vHistCfgs.emplace_back(h.GetName(), fsName);
+    };
+
+    std::for_each(fHistograms.fvH1.begin(), fHistograms.fvH1.end(), RegHist);
+    std::for_each(fHistograms.fvH2.begin(), fHistograms.fvH2.end(), RegHist);
+    std::for_each(fHistograms.fvP1.begin(), fHistograms.fvP1.end(), RegHist);
+    std::for_each(fHistograms.fvP2.begin(), fHistograms.fvP2.end(), RegHist);
 
     // Forming a canvas config message
     std::vector<std::pair<std::string, std::string>> vCanvCfgs;
@@ -47,12 +47,12 @@ void Data::Init(std::shared_ptr<HistogramSender> histSender)
 
     histSender->PrepareAndSendMsg(std::pair<uint32_t, uint32_t>(vHistCfgs.size(), vCanvCfgs.size()),
                                   zmq::send_flags::sndmore);
-    for (const auto& cfg : vHistCfgs) {
-      histSender->PrepareAndSendMsg(cfg, zmq::send_flags::sndmore);
-    }
-    for (const auto& cfg : vCanvCfgs) {
-      histSender->PrepareAndSendMsg(cfg, zmq::send_flags::sndmore);
-    }
+
+    auto RegCfg = [&](const auto& cfg) { histSender->PrepareAndSendMsg(cfg, zmq::send_flags::sndmore); };
+
+    std::for_each(vHistCfgs.begin(), vHistCfgs.end(), RegCfg);
+    std::for_each(vCanvCfgs.begin(), vCanvCfgs.end(), RegCfg);
+
     // Histograms serialization and emission to close multi-part message
     histSender->PrepareAndSendMsg(qa::HistogramContainer{}, zmq::send_flags::none);
   }
diff --git a/core/qa/report/CbmQaReportElement.h b/core/qa/report/CbmQaReportElement.h
index e469f10425..ef4d84315e 100644
--- a/core/qa/report/CbmQaReportElement.h
+++ b/core/qa/report/CbmQaReportElement.h
@@ -54,7 +54,7 @@ namespace cbm::qa::report
     void SetTitle(std::string_view title) { fsTitle = title; }
 
     /// \brief Gets mother element
-    const Element* const GetMother() const { return fpMother; }
+    const Element* GetMother() const { return fpMother; }
 
    private:
     void AssignMother(const Element* pMother) { fpMother = pMother; }
diff --git a/services/histserv/app/Application.cxx b/services/histserv/app/Application.cxx
index 39597c6832..bb763da0e7 100644
--- a/services/histserv/app/Application.cxx
+++ b/services/histserv/app/Application.cxx
@@ -203,35 +203,22 @@ bool Application::ReceiveData(zmq::message_t& msg)
   /// FIXME: Lead to "Warning in <TROOT::Append>: Replacing existing TH1: xxxxxx (Potential memory leak)."
 
   // Collect histograms
-  for (auto& source : vHist.fvH1) {
-    TH1* result = cbm::qa::OnlineInterface::ROOTHistogram(source);
-    if (!ReadHistogram<TH1>(result)) {  //
-      return false;
-    }
-    delete result;
-  }
-  for (auto& source : vHist.fvH2) {
-    TH1* result = cbm::qa::OnlineInterface::ROOTHistogram(source);
-    if (!ReadHistogram<TH1>(result)) {  //
-      return false;
-    }
-    delete result;
-  }
-  for (auto& source : vHist.fvP1) {
-    TH1* result = cbm::qa::OnlineInterface::ROOTHistogram(source);
-    if (!ReadHistogram<TH1>(result)) {  //
-      return false;
-    }
-    delete result;
-  }
-  for (auto& source : vHist.fvP2) {
-    TH1* result = cbm::qa::OnlineInterface::ROOTHistogram(source);
-    if (!ReadHistogram<TH1>(result)) {  //
-      return false;
+  auto CollectHistogram = [&](const auto& container) -> bool {
+    for (auto& source : container) {
+      TH1* hist = cbm::qa::OnlineInterface::ROOTHistogram(source);
+      if (!ReadHistogram<TH1>(hist)) {
+        delete hist;
+        return false;
+      }
+      delete hist;
     }
-    delete result;
-  }
+    return true;
+  };
 
+  if (!CollectHistogram(vHist.fvH1)) return false;
+  if (!CollectHistogram(vHist.fvH2)) return false;
+  if (!CollectHistogram(vHist.fvP1)) return false;
+  if (!CollectHistogram(vHist.fvP2)) return false;
 
   /// If new histos received, try to prepare as many canvases as possible
   /// Should be expensive on start and cheap afterward
@@ -321,6 +308,8 @@ bool Application::ReceiveData(zmq::message_t& msg)
 //
 bool Application::ReceiveHistoConfig(zmq::message_t& msg)
 {
+  using cbm::algo::qa::EHistFlag;
+  using cbm::algo::qa::HistogramMetadata;
   /// FIXME: Something to replace FairMQ and extract the config!!!!
   //  BoostSerializer<std::pair<std::string, std::string>>().Deserialize(msg, tempObject);
   b_io::basic_array_source<char> device(static_cast<char*>(msg.data()), msg.size());
@@ -343,29 +332,22 @@ bool Application::ReceiveHistoConfig(zmq::message_t& msg)
     iarch >> tempObject;
   }
 
-  LOG(debug) << " Received configuration for histo " << tempObject.first << " : " << tempObject.second;
-
-  /// Check if histo name already received in previous messages
-  /// Linear search should be ok as config is shared only at startup
-  UInt_t uPrevHist = 0;
-  for (uPrevHist = 0; uPrevHist < fvpsHistosFolder.size(); ++uPrevHist) {
-    if (fvpsHistosFolder[uPrevHist].first == tempObject.first) {  //
-      break;
-    }
-  }  // for( UInt_t uPrevHist = 0; uPrevHist < fvpsHistosFolder.size(); ++uPrevHist )
-
-  if (uPrevHist < fvpsHistosFolder.size()) {
-    LOG(debug) << " Ignored new configuration for histo " << tempObject.first
-               << " due to previously received one: " << tempObject.second;
-    /// Not sure if we should return false here...
-  }  // if( uPrevHist < fvpsHistosFolder.size() )
-  else {
-    fvpsHistosFolder.push_back(tempObject);
-    fvHistos.push_back(std::pair<TNamed*, std::string>(nullptr, ""));
-    fvbHistoRegistered.push_back(false);
-    fbAllHistosRegistered = false;
-    LOG(info) << " Stored configuration for histo " << tempObject.first << " : " << tempObject.second;
-  }  // else of if( uPrevHist < fvpsHistosFolder.size() )
+  // Parse metadata
+  LOG(info) << "\n\n\n!!!!!! tempObect-1: " << tempObject.first << ", " << tempObject.second << "\n\n";
+  std::string& name = tempObject.first;
+  std::string metadataMsg{};
+  std::tie(name, metadataMsg) = HistogramMetadata::SeparateNameAndMetadata(name);
+  LOG(info) << "\n\n\n!!!!!! tempObect-2: " << tempObject.first << ", " << tempObject.second << "\n\n";
+  auto metadata = HistogramMetadata(metadataMsg);
+
+  if (!metadata.GetFlag(EHistFlag::OmitIntegrated)) {
+    // Main (integrated over time) histogram
+    this->RegisterHistoConfig(tempObject);
+  }
+  if (metadata.GetFlag(EHistFlag::StoreVsTsId)) {
+    // Histogram vs. TS id
+    this->RegisterHistoConfig(std::make_pair(name + std::string(ksTsIdSuffix), tempObject.second));
+  }
 
   return true;
 }
@@ -520,6 +502,7 @@ bool Application::ReadHistogram(HistoT* pHist)
 {
   int index1 = FindHistogram(pHist->GetName());
   if (-1 == index1) {
+    // ----- Creating new histogram
     HistoT* histogram_new = static_cast<HistoT*>(pHist->Clone());
     fArrayHisto.Add(histogram_new);
 
@@ -558,6 +541,7 @@ bool Application::ReadHistogram(HistoT* pHist)
     }      // if( !fbAllCanvasReady )
   }        // if (-1 == index1)
   else {
+    // ----- Update histogram
     LOG(debug) << "Received update for: " << pHist->GetName();
     HistoT* histogram_existing = dynamic_cast<HistoT*>(fArrayHisto.At(index1));
     if (nullptr == histogram_existing) {
@@ -571,6 +555,37 @@ bool Application::ReadHistogram(HistoT* pHist)
   return true;
 }
 
+// ---------------------------------------------------------------------------------------------------------------------
+//
+bool Application::RegisterHistoConfig(const std::pair<std::string, std::string>& config)
+{
+  LOG(debug) << " Received configuration for histo " << config.first << " : " << config.second;
+
+  /// Check if histo name already received in previous messages
+  /// Linear search should be ok as config is shared only at startup
+  UInt_t uPrevHist = 0;
+  for (uPrevHist = 0; uPrevHist < fvpsHistosFolder.size(); ++uPrevHist) {
+    if (fvpsHistosFolder[uPrevHist].first == config.first) {  //
+      break;
+    }
+  }  // for( UInt_t uPrevHist = 0; uPrevHist < fvpsHistosFolder.size(); ++uPrevHist )
+
+  if (uPrevHist < fvpsHistosFolder.size()) {
+    LOG(debug) << " Ignored new configuration for histo " << config.first
+               << " due to previously received one: " << config.second;
+    /// Not sure if we should return false here...
+  }  // if( uPrevHist < fvpsHistosFolder.size() )
+  else {
+    fvpsHistosFolder.push_back(config);
+    fvHistos.push_back(std::pair<TNamed*, std::string>(nullptr, ""));
+    fvbHistoRegistered.push_back(false);
+    fbAllHistosRegistered = false;
+    LOG(info) << " Stored configuration for histo " << config.first << " : " << config.second;
+  }  // else of if( uPrevHist < fvpsHistosFolder.size() )
+
+  return true;
+}
+
 // ---------------------------------------------------------------------------------------------------------------------
 //
 int Application::FindHistogram(const std::string& name)
diff --git a/services/histserv/app/Application.h b/services/histserv/app/Application.h
index 2a14d8124b..7d7d55d22e 100644
--- a/services/histserv/app/Application.h
+++ b/services/histserv/app/Application.h
@@ -5,6 +5,7 @@
 #ifndef CBM_SERVICES_HISTSERV_APP_APPLICATION_H
 #define CBM_SERVICES_HISTSERV_APP_APPLICATION_H 1
 
+#include "Histogram.h"
 #include "ProgramOptions.h"
 #include "THttpServer.h"
 #include "TObjArray.h"
@@ -13,6 +14,7 @@
 #include <csignal>
 #include <memory>
 #include <string>
+#include <string_view>
 #include <thread>
 #include <zmq.hpp>
 
@@ -22,67 +24,77 @@ class TNamed;
 namespace cbm::services::histserv
 {
   class Application {
-  public:
+    static constexpr std::string_view ksTsIdSuffix = "_ts_id";  ///< Suffix of additional histograms vs. TS index
+
+   public:
     /** @brief Standard constructor, initialises the application
      ** @param opt  **/
-   explicit Application(ProgramOptions const& opt, volatile sig_atomic_t* signalStatus);
+    explicit Application(ProgramOptions const& opt, volatile sig_atomic_t* signalStatus);
 
-   /** @brief Copy constructor forbidden **/
-   Application(const Application&) = delete;
+    /** @brief Copy constructor forbidden **/
+    Application(const Application&) = delete;
 
-   /** @brief Assignment operator forbidden **/
-   void operator=(const Application&) = delete;
+    /** @brief Assignment operator forbidden **/
+    void operator=(const Application&) = delete;
 
-   /** @brief Destructor **/
-   ~Application();
+    /** @brief Destructor **/
+    ~Application();
 
-   /** @brief Run the application **/
-   void Exec();
+    /** @brief Run the application **/
+    void Exec();
 
-   void UpdateHttpServer();
+    void UpdateHttpServer();
 
-  private:
+   private:
     //const std::string& ConfigFile() const;
+    /// \param name  A name of the histogram
+    int FindHistogram(const std::string& name);
 
-   /// \brief Receives histograms
-   bool ReceiveData(zmq::message_t& msg);
+    /// \brief Resets handled histograms
+    bool ResetHistograms();
 
-   /// \brief Receives histogram configuration
-   bool ReceiveHistoConfig(zmq::message_t& msg);
+    /// \brief  Read a histogram
+    /// \tparam HistoT  Histogram type
+    /// \param  pHist   Pointer to the histogram
+    template<class HistoT>
+    bool ReadHistogram(HistoT* pHist);
 
-   /// \brief Receives canvas configuration
-   bool ReceiveCanvasConfig(zmq::message_t& msg);
+    /// \brief Find histogram index in the histogram array
+    /// \brief Receives histograms
+    bool ReceiveData(zmq::message_t& msg);
 
-   /// \brief Receives a list of canvases and histograms
-   /// \param vMsg  Message with the histograms and canvases list
-   bool ReceiveConfigAndData(std::vector<zmq::message_t>& vMsg);
+    /// \brief Receives histogram configuration
+    bool ReceiveHistoConfig(zmq::message_t& msg);
 
-   /// \brief  Read a histogram
-   /// \tparam HistoT  Histogram type
-   /// \param  pHist   Pointer to the histogram
-   template<class HistoT>
-   bool ReadHistogram(HistoT* pHist);
+    /// \brief Receives canvas configuration
+    bool ReceiveCanvasConfig(zmq::message_t& msg);
 
-   /// \brief Find histogram index in the histogram array
-   /// \param name  A name of the histogram
-   int FindHistogram(const std::string& name);
+    /// \brief Receives a list of canvases and histograms
+    /// \param vMsg  Message with the histograms and canvases list
+    bool ReceiveConfigAndData(std::vector<zmq::message_t>& vMsg);
 
-   /// \brief Prepares canvases using received canvas configuration
-   /// \param uCanvIdx Index of canvas
-   bool PrepareCanvas(uint32_t uCanvIdx);
+    /// \brief Register a histogram  config in the histogram server
+    /// \param config  A pair (histogram name, histogram directory)
+    ///
+    /// This function should be called after the metadata is extracted from the config.
+    bool RegisterHistoConfig(const std::pair<std::string, std::string>& config);
 
-   /// \brief Resets handled histograms
-   bool ResetHistograms();
+    /// \brief Register a histogram in the histogram server
+    /// \param hist A pointer to histogram
+    bool RegisterHistogram(const TNamed* hist);
 
-   /// \brief Saves handled histograms
-   bool SaveHistograms();
+    /// \brief Prepares canvases using received canvas configuration
+    /// \param uCanvIdx Index of canvas
+    bool PrepareCanvas(uint32_t uCanvIdx);
 
-   /// \brief A handler for system signals
-   /// \param signal  Signal ID
-   //static void SignalHandler(int signal);
+    /// \brief Saves handled histograms
+    bool SaveHistograms();
 
+    /// \brief A handler for system signals
+    /// \param signal  Signal ID
+    //static void SignalHandler(int signal);
 
-  private:
+   private:
     ProgramOptions const& fOpt;      ///< Program options object
     volatile sig_atomic_t* fSignalStatus;  ///< Global signal status
     THttpServer* fServer = nullptr;  ///< ROOT Histogram server (JSroot)
-- 
GitLab