From 57b89c82d9384ed0c48ad0be5cbdf37321efa920 Mon Sep 17 00:00:00 2001
From: "s.zharko@gsi.de" <s.zharko@gsi.de>
Date: Mon, 10 Feb 2025 00:28:32 +0100
Subject: [PATCH 1/4] online: processing of multiple tasks in a single qa::Data
 instance

---
 algo/qa/QaData.cxx       | 52 +++++++++++++++++++++++++++-------------
 algo/qa/QaData.h         | 42 +++++++++++++++++++++-----------
 algo/qa/TaskProperties.h | 33 +++++++++++++++++++++++++
 3 files changed, 97 insertions(+), 30 deletions(-)
 create mode 100644 algo/qa/TaskProperties.h

diff --git a/algo/qa/QaData.cxx b/algo/qa/QaData.cxx
index 1ead168f18..235bb3ddbf 100644
--- a/algo/qa/QaData.cxx
+++ b/algo/qa/QaData.cxx
@@ -13,6 +13,7 @@
 
 using cbm::algo::qa::Data;
 
+
 // ---------------------------------------------------------------------------------------------------------------------
 //
 void Data::Init(std::shared_ptr<HistogramSender> histSender)
@@ -28,20 +29,22 @@ try {
     nHistograms += std::distance(fHistograms.fvP2.begin(), fHistograms.fvP2.end());
     vHistCfgs.reserve(nHistograms);
 
-    auto RegHist = [&](const auto& h) {
-      if (!h.GetMetadata().CheckFlags()) {
-        std::stringstream msg;
-        msg << "attempt to pass a histogram " << h.GetName()
-            << " with inconsistent flags (see HistogramMetadata::CheckFlags for detailes)";
-        throw std::runtime_error(msg.str());
-      }
-      vHistCfgs.emplace_back(h.GetName() + "!" + h.GetMetadataString(), 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);
+    for (const auto& task: fvTaskProperties) {
+      auto RegHist = [&](const auto& h) {
+        if (!h.GetMetadata().CheckFlags()) {
+          std::stringstream msg;
+          msg << "attempt to pass a histogram " << h.GetName()
+              << " with inconsistent flags (see HistogramMetadata::CheckFlags for detailes)";
+          throw std::runtime_error(msg.str());
+        }
+        vHistCfgs.emplace_back(h.GetName() + "!" + h.GetMetadataString(), task.fsName);
+      };
+      fsTaskNames += fmt::format("{} ", task.fsName);
+      std::for_each(task.fRangeH1.first, task.fRangeH1.second, RegHist);
+      std::for_each(task.fRangeH2.first, task.fRangeH2.second, RegHist);
+      std::for_each(task.fRangeP1.first, task.fRangeP1.second, RegHist);
+      std::for_each(task.fRangeP2.first, task.fRangeP2.second, RegHist);
+    }
 
     // Forming a canvas config message
     std::vector<std::pair<std::string, std::string>> vCanvCfgs;
@@ -63,10 +66,27 @@ try {
   }
 }
 catch (const std::exception& err) {
-  L_(fatal) << "cbm::algo::qa::Data for " << fsName << " fatally aborted. Reason " << err.what();
+  L_(fatal) << "cbm::algo::qa::Data for " << fsTaskNames << " fatally aborted. Reason " << err.what();
   assert(false);
 }
 
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void Data::RegisterNewTask(std::string_view name)
+{
+  auto itH1 = fHistograms.fvH1.begin();
+  auto itH2 = fHistograms.fvH2.begin();
+  auto itP1 = fHistograms.fvP1.begin();
+  auto itP2 = fHistograms.fvP2.begin();
+  fvTaskProperties.emplace_back(TaskProperties{
+    .fsName   = {name.begin(), name.end()},
+    .fRangeH1 = std::make_pair(itH1, itH1),
+    .fRangeH2 = std::make_pair(itH2, itH2),
+    .fRangeP1 = std::make_pair(itP1, itP1),
+    .fRangeP2 = std::make_pair(itP2, itP2)
+  });
+}
+
 // ---------------------------------------------------------------------------------------------------------------------
 //
 void Data::Send(std::shared_ptr<HistogramSender> histoSender)
@@ -76,7 +96,7 @@ void Data::Send(std::shared_ptr<HistogramSender> histoSender)
   auto nH2 = std::distance(fHistograms.fvH2.begin(), fHistograms.fvH2.end());
   auto nP1 = std::distance(fHistograms.fvP1.begin(), fHistograms.fvP1.end());
   auto nP2 = std::distance(fHistograms.fvP2.begin(), fHistograms.fvP2.end());
-  L_(info) << fsName << ": Published " << nH1 << " 1D- and " << nH2 << " 2D-histograms, " << nP1 << " 1D- and " << nP2
+  L_(info) << fsTaskNames << ": Published " << nH1 << " 1D- and " << nH2 << " 2D-histograms, " << nP1 << " 1D- and " << nP2
            << " 2D-profiles";
   this->Reset();
 }
diff --git a/algo/qa/QaData.h b/algo/qa/QaData.h
index 0c87df69c5..fcb1afced2 100644
--- a/algo/qa/QaData.h
+++ b/algo/qa/QaData.h
@@ -10,9 +10,10 @@
 #pragma once
 
 #include "AlgoFairloggerCompat.h"
-#include "CanvasConfig.h"
-#include "HistogramContainer.h"
-#include "HistogramSender.h"
+#include "qa/CanvasConfig.h"
+#include "qa/HistogramContainer.h"
+#include "qa/TaskProperties.h"
+#include "base/HistogramSender.h"
 
 #include <boost/serialization/forward_list.hpp>
 
@@ -27,9 +28,12 @@ namespace cbm::algo::qa
   /// \brief Class to handle QA-objects in the online reconstruction
   class Data {
    public:
+    /// \brief Default constructor
+    Data() = default;
+
     /// \brief Constructor
     /// \param name  Name of the QA module (appears as the directory name in the output)
-    Data(std::string_view name) : fsName(name) {}
+    Data(std::string_view name) { RegisterNewTask(name); }
 
     /// \brief Copy constructor
     Data(const Data&) = default;
@@ -50,9 +54,6 @@ namespace cbm::algo::qa
     /// \param canvas  A CanvasConfig object
     void AddCanvasConfig(const CanvasConfig& canvas) { fvsCanvCfgs.push_back(canvas.ToString()); }
 
-    /// \brief Gets module name
-    std::string_view GetName() const { return fsName; }
-
     /// \brief Sends QA initialization information to the HistogramSender
     /// \param histoSender  A pointer to the histogram sender
     void Init(std::shared_ptr<HistogramSender> histoSender);
@@ -73,10 +74,15 @@ namespace cbm::algo::qa
     /// \param timesliceId  Timeslice index
     void SetTimesliceId(uint64_t timesliceId) { fHistograms.fTimesliceId = timesliceId; }
 
+    /// \brief Registers a new QA task
+    /// \param name  Name of the task
+    void RegisterNewTask(std::string_view name);
+
    private:
-    std::string fsName;                         ///< Name of the QA module (used as a directory name)
-    qa::HistogramContainer fHistograms;         ///< Histograms container
-    std::vector<std::string> fvsCanvCfgs = {};  ///< Vector of canvas configs
+    qa::HistogramContainer fHistograms;                ///< Histograms container
+    std::string fsTaskNames;                           ///< A string containing names of tasks
+    std::vector<qa::TaskProperties> fvTaskProperties;  ///< A vector to store properties for multiple QA-tasks
+    std::vector<std::string> fvsCanvCfgs = {};         ///< Vector of canvas configs
   };
 
   // -------------------------------------------------------------------------------------------------------------------
@@ -85,16 +91,24 @@ namespace cbm::algo::qa
   Obj* Data::MakeObj(Args... args)
   {
     if constexpr (std::is_same_v<Obj, cbm::algo::qa::H1D>) {
-      return &(fHistograms.fvH1.emplace_front(args...));
+      Obj* res = &(fHistograms.fvH1.emplace_front(args...));
+      fvTaskProperties.back().fRangeH1.first = fHistograms.fvH1.begin();
+      return res;
     }
     else if constexpr (std::is_same_v<Obj, cbm::algo::qa::H2D>) {
-      return &(fHistograms.fvH2.emplace_front(args...));
+      Obj* res = &(fHistograms.fvH2.emplace_front(args...));
+      fvTaskProperties.back().fRangeH2.first = fHistograms.fvH2.begin();
+      return res;
     }
     else if constexpr (std::is_same_v<Obj, cbm::algo::qa::Prof1D>) {
-      return &(fHistograms.fvP1.emplace_front(args...));
+      Obj* res = &(fHistograms.fvP1.emplace_front(args...));
+      fvTaskProperties.back().fRangeP1.first = fHistograms.fvP1.begin();
+      return res;
     }
     else if constexpr (std::is_same_v<Obj, cbm::algo::qa::Prof2D>) {
-      return &(fHistograms.fvP2.emplace_front(args...));
+      Obj* res = &(fHistograms.fvP2.emplace_front(args...));
+      fvTaskProperties.back().fRangeP2.first = fHistograms.fvP2.begin();
+      return res;
     }
     return nullptr;
   }
diff --git a/algo/qa/TaskProperties.h b/algo/qa/TaskProperties.h
new file mode 100644
index 0000000000..377be8d777
--- /dev/null
+++ b/algo/qa/TaskProperties.h
@@ -0,0 +1,33 @@
+/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   TaskProperties.h
+/// \date   09.02.2025
+/// \brief  QA-task properties structure
+/// \author Sergei Zharko <s.zharko@gsi.de>
+
+#pragma once
+
+#include "Histogram.h"  // for H1D, H2D
+
+#include <forward_list>
+#include <string>
+#include <utility>
+
+namespace cbm::algo::qa
+{
+  /// \struct HistogramContainer
+  /// \brief  Structure to keep the histograms for sending them on the histogram server
+  struct TaskProperties {
+    template <class H>
+    using IteratorPair_t = std::pair<typename std::forward_list<H>::iterator, typename std::forward_list<H>::iterator>;
+
+    std::string fsName;                   ///< Name of the task
+    IteratorPair_t<qa::H1D> fRangeH1;     ///< A pair (begin, end) for 1D-histograms in the task
+    IteratorPair_t<qa::H2D> fRangeH2;     ///< A pair (begin, end) for 2D-histograms in the task
+    IteratorPair_t<qa::Prof1D> fRangeP1;  ///< A pair (begin, end) for 1D-profiles in the task
+    IteratorPair_t<qa::Prof2D> fRangeP2;  ///< A pair (begin, end) for 2D-profiles in the task
+  };
+}  // namespace cbm::algo::qa
+
-- 
GitLab


From 597d2276664ff8479fc403d25d117917260c2272 Mon Sep 17 00:00:00 2001
From: "s.zharko@gsi.de" <s.zharko@gsi.de>
Date: Mon, 10 Feb 2025 13:20:57 +0100
Subject: [PATCH 2/4] updates on online QA + bugfix of histogram server

---
 algo/qa/QaData.cxx                   | 44 ++++++++++++---------
 algo/qa/QaData.h                     | 13 +++++-
 algo/qa/QaManager.cxx                | 26 ++++++++++++
 algo/qa/QaManager.h                  | 59 ++++++++++++++++++++++++++++
 algo/qa/TaskProperties.h             |  3 +-
 services/histserv/app/CMakeLists.txt |  2 +-
 6 files changed, 124 insertions(+), 23 deletions(-)
 create mode 100644 algo/qa/QaManager.cxx
 create mode 100644 algo/qa/QaManager.h

diff --git a/algo/qa/QaData.cxx b/algo/qa/QaData.cxx
index 235bb3ddbf..5abee21fe5 100644
--- a/algo/qa/QaData.cxx
+++ b/algo/qa/QaData.cxx
@@ -19,17 +19,31 @@ using cbm::algo::qa::Data;
 void Data::Init(std::shared_ptr<HistogramSender> histSender)
 try {
   if (histSender.get()) {
+    // Check, if the tasks list was initialized properly: at least one task must be initialized
+    if (fvTaskProperties.empty()) {
+      std::stringstream msg;
+      msg << "a qa::Data instance was not initialized properly: no task was registered. The list of the histograms:\n";
+      auto ShowName = [&](const auto& h) { msg << " - " << h.GetName() << '\n'; };
+      std::for_each(fHistograms.fvH1.begin(), fHistograms.fvH1.end(), ShowName);
+      std::for_each(fHistograms.fvH2.begin(), fHistograms.fvH2.end(), ShowName);
+      std::for_each(fHistograms.fvP1.begin(), fHistograms.fvP1.end(), ShowName);
+      std::for_each(fHistograms.fvP2.begin(), fHistograms.fvP2.end(), ShowName);
+      msg << "Please, insure that you either instantiate the qa::Data with the Data(std::string_view name) constructor";
+      msg << ", or provide a task name explicitly with the function Data::RegisterNewTask(std::string_view name)";
+      throw std::runtime_error(msg.str());
+    }
+
     // Forming a histogram config message
     std::vector<std::pair<std::string, std::string>> vHistCfgs;
     size_t nHistograms = 0;
     // NOTE: Important to keep the order of filling the histograms: 1D -> 2D -> ..
-    nHistograms += std::distance(fHistograms.fvH1.begin(), fHistograms.fvH1.end());
-    nHistograms += std::distance(fHistograms.fvH2.begin(), fHistograms.fvH2.end());
-    nHistograms += std::distance(fHistograms.fvP1.begin(), fHistograms.fvP1.end());
-    nHistograms += std::distance(fHistograms.fvP2.begin(), fHistograms.fvP2.end());
+    nHistograms += fNofH1;
+    nHistograms += fNofH2;
+    nHistograms += fNofP1;
+    nHistograms += fNofP2;
     vHistCfgs.reserve(nHistograms);
 
-    for (const auto& task: fvTaskProperties) {
+    for (const auto& task : fvTaskProperties) {
       auto RegHist = [&](const auto& h) {
         if (!h.GetMetadata().CheckFlags()) {
           std::stringstream msg;
@@ -78,13 +92,11 @@ void Data::RegisterNewTask(std::string_view name)
   auto itH2 = fHistograms.fvH2.begin();
   auto itP1 = fHistograms.fvP1.begin();
   auto itP2 = fHistograms.fvP2.begin();
-  fvTaskProperties.emplace_back(TaskProperties{
-    .fsName   = {name.begin(), name.end()},
-    .fRangeH1 = std::make_pair(itH1, itH1),
-    .fRangeH2 = std::make_pair(itH2, itH2),
-    .fRangeP1 = std::make_pair(itP1, itP1),
-    .fRangeP2 = std::make_pair(itP2, itP2)
-  });
+  fvTaskProperties.emplace_back(TaskProperties{.fsName   = {name.begin(), name.end()},
+                                               .fRangeH1 = std::make_pair(itH1, itH1),
+                                               .fRangeH2 = std::make_pair(itH2, itH2),
+                                               .fRangeP1 = std::make_pair(itP1, itP1),
+                                               .fRangeP2 = std::make_pair(itP2, itP2)});
 }
 
 // ---------------------------------------------------------------------------------------------------------------------
@@ -92,11 +104,7 @@ void Data::RegisterNewTask(std::string_view name)
 void Data::Send(std::shared_ptr<HistogramSender> histoSender)
 {
   histoSender->PrepareAndSendMsg(fHistograms, zmq::send_flags::none);
-  auto nH1 = std::distance(fHistograms.fvH1.begin(), fHistograms.fvH1.end());
-  auto nH2 = std::distance(fHistograms.fvH2.begin(), fHistograms.fvH2.end());
-  auto nP1 = std::distance(fHistograms.fvP1.begin(), fHistograms.fvP1.end());
-  auto nP2 = std::distance(fHistograms.fvP2.begin(), fHistograms.fvP2.end());
-  L_(info) << fsTaskNames << ": Published " << nH1 << " 1D- and " << nH2 << " 2D-histograms, " << nP1 << " 1D- and " << nP2
-           << " 2D-profiles";
+  L_(info) << fsTaskNames << ": Published " << fNofH1 << " 1D- and " << fNofH2 << " 2D-histograms, " << fNofP1
+           << " 1D- and " << fNofP2 << " 2D-profiles";
   this->Reset();
 }
diff --git a/algo/qa/QaData.h b/algo/qa/QaData.h
index fcb1afced2..9c47474401 100644
--- a/algo/qa/QaData.h
+++ b/algo/qa/QaData.h
@@ -10,10 +10,10 @@
 #pragma once
 
 #include "AlgoFairloggerCompat.h"
+#include "base/HistogramSender.h"
 #include "qa/CanvasConfig.h"
 #include "qa/HistogramContainer.h"
 #include "qa/TaskProperties.h"
-#include "base/HistogramSender.h"
 
 #include <boost/serialization/forward_list.hpp>
 
@@ -79,10 +79,15 @@ namespace cbm::algo::qa
     void RegisterNewTask(std::string_view name);
 
    private:
-    qa::HistogramContainer fHistograms;                ///< Histograms container
+    qa::HistogramContainer fHistograms;                ///< A container of histograms, which forms a zmq message
     std::string fsTaskNames;                           ///< A string containing names of tasks
     std::vector<qa::TaskProperties> fvTaskProperties;  ///< A vector to store properties for multiple QA-tasks
     std::vector<std::string> fvsCanvCfgs = {};         ///< Vector of canvas configs
+
+    uint32_t fNofH1{0};  ///< Number of 1D-histograms
+    uint32_t fNofH2{0};  ///< Number of 2D-histograms
+    uint32_t fNofP1{0};  ///< Number of 1D-profiles
+    uint32_t fNofP2{0};  ///< Number of 2D-profiles
   };
 
   // -------------------------------------------------------------------------------------------------------------------
@@ -92,21 +97,25 @@ namespace cbm::algo::qa
   {
     if constexpr (std::is_same_v<Obj, cbm::algo::qa::H1D>) {
       Obj* res = &(fHistograms.fvH1.emplace_front(args...));
+      ++fNofH1;
       fvTaskProperties.back().fRangeH1.first = fHistograms.fvH1.begin();
       return res;
     }
     else if constexpr (std::is_same_v<Obj, cbm::algo::qa::H2D>) {
       Obj* res = &(fHistograms.fvH2.emplace_front(args...));
+      ++fNofH2;
       fvTaskProperties.back().fRangeH2.first = fHistograms.fvH2.begin();
       return res;
     }
     else if constexpr (std::is_same_v<Obj, cbm::algo::qa::Prof1D>) {
       Obj* res = &(fHistograms.fvP1.emplace_front(args...));
+      ++fNofP1;
       fvTaskProperties.back().fRangeP1.first = fHistograms.fvP1.begin();
       return res;
     }
     else if constexpr (std::is_same_v<Obj, cbm::algo::qa::Prof2D>) {
       Obj* res = &(fHistograms.fvP2.emplace_front(args...));
+      ++fNofP2;
       fvTaskProperties.back().fRangeP2.first = fHistograms.fvP2.begin();
       return res;
     }
diff --git a/algo/qa/QaManager.cxx b/algo/qa/QaManager.cxx
new file mode 100644
index 0000000000..dc831fa2ec
--- /dev/null
+++ b/algo/qa/QaManager.cxx
@@ -0,0 +1,26 @@
+/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   QaManager.cxx
+/// \date   09.02.2025
+/// \brief  QA manager for the online data reconstruction
+/// \author Sergei Zharko <s.zharko@gsi.de>
+
+#include "qa/QaManager.h"
+
+using cbm::algo::HistogramSender;
+using cbm::algo::qa::Data;
+using cbm::algo::qa::Manager;
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+Manager::Manager(std::shared_ptr<HistogramSender> histoSender) : fpSender(histoSender) {}
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void Manager::Init() { fpData->Init(fpSender); }
+
+// ---------------------------------------------------------------------------------------------------------------------
+//
+void Manager::SendHistograms() { fpData->Send(fpSender); }
diff --git a/algo/qa/QaManager.h b/algo/qa/QaManager.h
new file mode 100644
index 0000000000..4a291800c5
--- /dev/null
+++ b/algo/qa/QaManager.h
@@ -0,0 +1,59 @@
+/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   QaManager.h
+/// \date   09.02.2025
+/// \brief  QA manager for the online data reconstruction
+/// \author Sergei Zharko <s.zharko@gsi.de>
+
+#pragma once
+
+#include "base/HistogramSender.h"
+#include "base/SubChain.h"
+#include "qa/QaData.h"
+
+namespace cbm::algo::qa
+{
+  /// \class Manager
+  /// \brief A central class to manage the histogram storage and sending to the histogram server
+  class Manager : public SubChain {
+   public:
+    /// \brief Constructor
+    /// \param histoSender  A histogram sender instance
+    Manager(std::shared_ptr<HistogramSender> histoSender = nullptr);
+
+    /// \brief Copy constructor
+    Manager(const Manager&) = delete;
+
+    /// \brief Move constructor
+    Manager(Manager&&) = delete;
+
+    /// \brief Destructor
+    ~Manager() = delete;
+
+    /// \brief Copy assignment operator
+    Manager& operator=(const Manager&) = delete;
+
+    /// \brief Move assignment operator
+    Manager& operator=(Manager&&) = delete;
+
+    /// \brief Gets an instance of QA data
+    std::shared_ptr<Data> GetData() { return fpData; }
+
+    /// \brief Initializes the instance and sends the histogram and canvas configuration to the server
+    void Init();
+
+    /// \brief Sends a collection of histograms to the server
+    /// \note  Resets the histograms after sending them
+    void SendHistograms();
+
+    /// \brief Sets a timeslice index
+    /// \param timesliceId  A timeslice index
+    void SetTimesliceId(uint64_t timesliceId) { fpData->SetTimesliceId(timesliceId); }
+
+   private:
+    std::shared_ptr<HistogramSender> fpSender{nullptr};      ///< Histogram sender
+    std::shared_ptr<Data> fpData{std::make_shared<Data>()};  ///< Instance of QA Data
+  };
+}  // namespace cbm::algo::qa
diff --git a/algo/qa/TaskProperties.h b/algo/qa/TaskProperties.h
index 377be8d777..ec012b907c 100644
--- a/algo/qa/TaskProperties.h
+++ b/algo/qa/TaskProperties.h
@@ -20,7 +20,7 @@ namespace cbm::algo::qa
   /// \struct HistogramContainer
   /// \brief  Structure to keep the histograms for sending them on the histogram server
   struct TaskProperties {
-    template <class H>
+    template<class H>
     using IteratorPair_t = std::pair<typename std::forward_list<H>::iterator, typename std::forward_list<H>::iterator>;
 
     std::string fsName;                   ///< Name of the task
@@ -30,4 +30,3 @@ namespace cbm::algo::qa
     IteratorPair_t<qa::Prof2D> fRangeP2;  ///< A pair (begin, end) for 2D-profiles in the task
   };
 }  // namespace cbm::algo::qa
-
diff --git a/services/histserv/app/CMakeLists.txt b/services/histserv/app/CMakeLists.txt
index 1435a08a01..7a8ebfe9f8 100644
--- a/services/histserv/app/CMakeLists.txt
+++ b/services/histserv/app/CMakeLists.txt
@@ -28,7 +28,7 @@ add_executable(histserv_nofairmq ${SRCS} ${HEADERS})
 
 target_link_libraries(histserv_nofairmq
   PUBLIC
-    Algo
+    AlgoOffline
     CbmQaBase
     CbmFlibFlesTools
     CbmServicesHistServ
-- 
GitLab


From 51870329caaa2b83b3ecb7874fbd5c994644583c Mon Sep 17 00:00:00 2001
From: "s.zharko@gsi.de" <s.zharko@gsi.de>
Date: Mon, 10 Feb 2025 14:44:09 +0100
Subject: [PATCH 3/4] online qa: task header and QA-manager in the online
 binary

---
 algo/CMakeLists.txt    |  1 +
 algo/global/Reco.cxx   | 19 +++++++++++--
 algo/global/Reco.h     |  4 +++
 algo/qa/QaData.cxx     | 28 +++++++++++++-------
 algo/qa/QaData.h       | 14 ++++++----
 algo/qa/QaManager.h    |  4 +--
 algo/qa/QaTaskHeader.h | 60 ++++++++++++++++++++++++++++++++++++++++++
 7 files changed, 111 insertions(+), 19 deletions(-)
 create mode 100644 algo/qa/QaTaskHeader.h

diff --git a/algo/CMakeLists.txt b/algo/CMakeLists.txt
index 5776eeb2aa..09ee075af5 100644
--- a/algo/CMakeLists.txt
+++ b/algo/CMakeLists.txt
@@ -150,6 +150,7 @@ set(SRCS
   qa/PadConfig.cxx
   qa/QaData.cxx
   qa/RecoGeneralQa.cxx
+  qa/QaManager.cxx
   qa/unpack/StsDigiQa.cxx
   ca/TrackingSetup.cxx
   ca/TrackingChain.cxx
diff --git a/algo/global/Reco.cxx b/algo/global/Reco.cxx
index 6ca23944bd..28acffb8b3 100644
--- a/algo/global/Reco.cxx
+++ b/algo/global/Reco.cxx
@@ -21,6 +21,7 @@
 #include "compat/OpenMP.h"
 #include "evbuild/Config.h"
 #include "much/Unpack.h"
+#include "qa/QaManager.h"
 #include "rich/Unpack.h"
 #include "sts/ChannelMaskSet.h"
 #include "sts/HitfinderChain.h"
@@ -111,8 +112,13 @@ void Reco::Init(const Options& opts)
   ParFiles parFiles(opts.RunId());
   L_(info) << "Using parameter files for setup " << parFiles.setup;
 
-  // General QA
+  // QA instantiation
   if (fSender != nullptr) {
+    // QA manager
+    fQaManager = std::make_unique<qa::Manager>(fSender);
+    fQaManager->SetContext(&fContext);
+
+    // General QA
     fGeneralQa = std::make_unique<qa::RecoGeneralQa>(fRunStartTimeNs, fSender);
   }
 
@@ -222,6 +228,11 @@ void Reco::Init(const Options& opts)
     fTracking->Init();
   }
 
+  // Initialize the QA manager
+  if (fQaManager != nullptr) {
+    fQaManager->Init();
+  }
+
   fInitialized = true;
 
   L_(debug) << "CBM Reco finished initialization";
@@ -379,9 +390,13 @@ RecoResults Reco::Run(const fles::Timeslice& ts)
       results.trdHits = std::move(recoData.trdHits);
     }
 
-    // General QA
+    // QA
     if (fSender != nullptr) {
       (*fGeneralQa)(ts);
+
+      // Send all the histograms, collected through the timeslice
+      fQaManager->SetTimesliceId(ts.index());
+      fQaManager->SendHistograms();
     }
   }
   PrintTimings(procMon.time);
diff --git a/algo/global/Reco.h b/algo/global/Reco.h
index a3fa9c2411..8e26d35271 100644
--- a/algo/global/Reco.h
+++ b/algo/global/Reco.h
@@ -89,6 +89,7 @@ namespace cbm::algo
   namespace qa
   {
     class RecoGeneralQa;
+    class Manager;
   }
 }  // namespace cbm::algo
 
@@ -173,6 +174,9 @@ namespace cbm::algo
     // Tracking
     std::unique_ptr<TrackingChain> fTracking;
 
+    // QA
+    std::unique_ptr<qa::Manager> fQaManager;
+
     static double FilterNan(double x) { return std::isnan(x) || std::isinf(x) ? 0. : x; }
 
     void Validate(const Options& opts);
diff --git a/algo/qa/QaData.cxx b/algo/qa/QaData.cxx
index 5abee21fe5..520ed4e522 100644
--- a/algo/qa/QaData.cxx
+++ b/algo/qa/QaData.cxx
@@ -18,7 +18,18 @@ using cbm::algo::qa::Data;
 //
 void Data::Init(std::shared_ptr<HistogramSender> histSender)
 try {
-  if (histSender.get()) {
+  size_t nHistograms = 0;
+  nHistograms += fNofH1;
+  nHistograms += fNofH2;
+  nHistograms += fNofP1;
+  nHistograms += fNofP2;
+  fbNotEmpty = static_cast<bool>(nHistograms);
+  if (!fbNotEmpty) {
+    L_(warn) << "no histograms were provided to a qa::Data instance (running in an idle mode)";
+  }
+
+  if (histSender.get() && fbNotEmpty) {
+
     // Check, if the tasks list was initialized properly: at least one task must be initialized
     if (fvTaskProperties.empty()) {
       std::stringstream msg;
@@ -35,12 +46,7 @@ try {
 
     // Forming a histogram config message
     std::vector<std::pair<std::string, std::string>> vHistCfgs;
-    size_t nHistograms = 0;
     // NOTE: Important to keep the order of filling the histograms: 1D -> 2D -> ..
-    nHistograms += fNofH1;
-    nHistograms += fNofH2;
-    nHistograms += fNofP1;
-    nHistograms += fNofP2;
     vHistCfgs.reserve(nHistograms);
 
     for (const auto& task : fvTaskProperties) {
@@ -103,8 +109,10 @@ void Data::RegisterNewTask(std::string_view name)
 //
 void Data::Send(std::shared_ptr<HistogramSender> histoSender)
 {
-  histoSender->PrepareAndSendMsg(fHistograms, zmq::send_flags::none);
-  L_(info) << fsTaskNames << ": Published " << fNofH1 << " 1D- and " << fNofH2 << " 2D-histograms, " << fNofP1
-           << " 1D- and " << fNofP2 << " 2D-profiles";
-  this->Reset();
+  if (histoSender.get() && fbNotEmpty) {
+    histoSender->PrepareAndSendMsg(fHistograms, zmq::send_flags::none);
+    L_(info) << fsTaskNames << ": Published " << fNofH1 << " 1D- and " << fNofH2 << " 2D-histograms, " << fNofP1
+             << " 1D- and " << fNofP2 << " 2D-profiles";
+    this->Reset();
+  }
 }
diff --git a/algo/qa/QaData.h b/algo/qa/QaData.h
index 9c47474401..8c66c445a9 100644
--- a/algo/qa/QaData.h
+++ b/algo/qa/QaData.h
@@ -58,7 +58,10 @@ namespace cbm::algo::qa
     /// \param histoSender  A pointer to the histogram sender
     void Init(std::shared_ptr<HistogramSender> histoSender);
 
-    /// \brief Creates a QA-object and returns the pointer to it
+    /// \brief  Creates a QA-object and returns the pointer to it
+    /// \tparam Obj      A type of the histogram (H1D, H2D, Prof1D, Prof2D)
+    /// \tparam Args...  A signature of the histogram constructor
+    /// \param  args     Parameters, passed to a histogram constructor
     template<class Obj, typename... Args>
     Obj* MakeObj(Args... args);
 
@@ -84,10 +87,11 @@ namespace cbm::algo::qa
     std::vector<qa::TaskProperties> fvTaskProperties;  ///< A vector to store properties for multiple QA-tasks
     std::vector<std::string> fvsCanvCfgs = {};         ///< Vector of canvas configs
 
-    uint32_t fNofH1{0};  ///< Number of 1D-histograms
-    uint32_t fNofH2{0};  ///< Number of 2D-histograms
-    uint32_t fNofP1{0};  ///< Number of 1D-profiles
-    uint32_t fNofP2{0};  ///< Number of 2D-profiles
+    uint32_t fNofH1{0};     ///< Number of 1D-histograms
+    uint32_t fNofH2{0};     ///< Number of 2D-histograms
+    uint32_t fNofP1{0};     ///< Number of 1D-profiles
+    uint32_t fNofP2{0};     ///< Number of 2D-profiles
+    bool fbNotEmpty{true};  ///< false: if no histograms were provided, do not perform initialization and sending
   };
 
   // -------------------------------------------------------------------------------------------------------------------
diff --git a/algo/qa/QaManager.h b/algo/qa/QaManager.h
index 4a291800c5..ab7e397c5f 100644
--- a/algo/qa/QaManager.h
+++ b/algo/qa/QaManager.h
@@ -30,7 +30,7 @@ namespace cbm::algo::qa
     Manager(Manager&&) = delete;
 
     /// \brief Destructor
-    ~Manager() = delete;
+    ~Manager() = default;
 
     /// \brief Copy assignment operator
     Manager& operator=(const Manager&) = delete;
@@ -39,7 +39,7 @@ namespace cbm::algo::qa
     Manager& operator=(Manager&&) = delete;
 
     /// \brief Gets an instance of QA data
-    std::shared_ptr<Data> GetData() { return fpData; }
+    std::shared_ptr<Data> GetData() const { return fpData; }
 
     /// \brief Initializes the instance and sends the histogram and canvas configuration to the server
     void Init();
diff --git a/algo/qa/QaTaskHeader.h b/algo/qa/QaTaskHeader.h
new file mode 100644
index 0000000000..4038cd0d3e
--- /dev/null
+++ b/algo/qa/QaTaskHeader.h
@@ -0,0 +1,60 @@
+/* Copyright (C) 2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Sergei Zharko [committer] */
+
+/// \file   QaTaskHeader.h
+/// \date   10.02.2025
+/// \brief  A header for a particular QA task, must be inherited by a given QA task
+/// \author Sergei Zharko <s.zharko@gsi.de>
+
+#pragma once
+
+#include "qa/QaManager.h"
+
+#include <memory>
+
+namespace cbm::algo::qa
+{
+  /// \class TaskHeader
+  /// \brief An interface to the qa::Manager
+  /// \note  Must be inherited by a QA task
+  class TaskHeader {
+   public:
+    /// \brief Constructor
+    /// \param pManager a QA-manager
+    TaskHeader(const std::unique_ptr<Manager>& pManager) : fpData(pManager->GetData()) {}
+
+    /// \brief Copy constructor
+    TaskHeader(const TaskHeader&) = delete;
+
+    /// \brief Move constructor
+    TaskHeader(TaskHeader&&) = delete;
+
+    /// \brief Destructor
+    ~TaskHeader() = default;
+
+    /// \brief Copy assignment operator
+    TaskHeader& operator=(const TaskHeader&) = delete;
+
+    /// \brief Move assignment operator
+    TaskHeader& operator=(TaskHeader&&) = delete;
+
+   protected:
+    /// \brief Adds a canvas configuration
+    /// \param canvas  A CanvasConfig object
+    void AddCanvasConfig(const CanvasConfig& canvas) { fpData->AddCanvasConfig(canvas); }
+
+    /// \brief  Creates a QA-object and returns the pointer to it
+    /// \tparam Obj      A type of the histogram (H1D, H2D, Prof1D, Prof2D)
+    /// \tparam Args...  A signature of the histogram constructor
+    /// \param  args     Parameters, passed to a histogram constructor
+    template<class Obj, typename... Args>
+    Obj* MakeObj(Args... args)
+    {
+      fpData->MakeObj<Obj>(args...);
+    }
+
+   private:
+    std::shared_ptr<Data> fpData{nullptr};  ///< An instance of the QA data (shared between different tasks)
+  };
+}  // namespace cbm::algo::qa
-- 
GitLab


From 9a7d6c300c4e4e1bdf177da1547f67d5097fe5c9 Mon Sep 17 00:00:00 2001
From: "s.zharko@gsi.de" <s.zharko@gsi.de>
Date: Mon, 10 Feb 2025 16:15:36 +0100
Subject: [PATCH 4/4] online-qa: moving the tracking QA on the new framework

---
 algo/ca/TrackingChain.cxx | 11 ++++++----
 algo/ca/TrackingChain.h   | 13 ++++++++----
 algo/ca/qa/CaQa.cxx       | 43 ++++++++++++++++++---------------------
 algo/ca/qa/CaQa.h         | 19 +++++++----------
 algo/global/Reco.cxx      | 11 +++++++---
 algo/qa/QaTaskHeader.h    | 17 ++++++++++++++--
 6 files changed, 66 insertions(+), 48 deletions(-)

diff --git a/algo/ca/TrackingChain.cxx b/algo/ca/TrackingChain.cxx
index 73b3d47f00..3810670e9f 100644
--- a/algo/ca/TrackingChain.cxx
+++ b/algo/ca/TrackingChain.cxx
@@ -1,4 +1,4 @@
-/* Copyright (C) 2023-2024 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+/* Copyright (C) 2023-2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
    SPDX-License-Identifier: GPL-3.0-only
    Authors: Sergei Zharko [committer] */
 
@@ -39,7 +39,10 @@ using cbm::algo::ca::constants::clrs::GNb;  // grin bald text
 
 // ---------------------------------------------------------------------------------------------------------------------
 //
-TrackingChain::TrackingChain(std::shared_ptr<cbm::algo::HistogramSender> histoSender) : fQa(Qa(histoSender)) {}
+TrackingChain::TrackingChain(const std::unique_ptr<cbm::algo::qa::Manager>& qaManager, std::string_view name)
+  : fQa(Qa(qaManager, name))
+{
+}
 
 // ---------------------------------------------------------------------------------------------------------------------
 //
@@ -90,7 +93,7 @@ void TrackingChain::Init()
   fCaFramework.Init(ca::TrackingMode::kMcbm);
 
   // ------ Initialize QA modules
-  if (fQa.IsSenderDefined()) {
+  if (fQa.IsActive()) {
     fQa.RegisterParameters(&fCaFramework.GetParameters());
     fQa.Init();
   }
@@ -199,7 +202,7 @@ TrackingChain::Output_t TrackingChain::PrepareOutput()
 
 
   // QA
-  if (fQa.IsSenderDefined()) {
+  if (fQa.IsActive()) {
     fCaMonitorData.StartTimer(ca::ETimer::Qa);
     fQa.RegisterInputData(&fCaFramework.GetInputData());
     fQa.RegisterTracks(&output.tracks);
diff --git a/algo/ca/TrackingChain.h b/algo/ca/TrackingChain.h
index e96735b5be..3f949d3605 100644
--- a/algo/ca/TrackingChain.h
+++ b/algo/ca/TrackingChain.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+/* Copyright (C) 2023-2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
    SPDX-License-Identifier: GPL-3.0-only
    Authors: Sergei Zharko [committer] */
 
@@ -15,7 +15,6 @@
 #include "CaTrack.h"
 #include "CaTrackingMonitor.h"
 #include "CaVector.h"
-#include "HistogramSender.h"
 #include "PartitionedSpan.h"
 #include "RecoResults.h"
 #include "SubChain.h"
@@ -28,6 +27,11 @@
 #include <memory>
 #include <vector>
 
+namespace cbm::algo::qa
+{
+  class Manager;
+}
+
 namespace cbm::algo
 {
   /// \class cbm::algo::TrackingChain
@@ -37,8 +41,9 @@ namespace cbm::algo
   class TrackingChain : public SubChain {
    public:
     /// \brief Constructor from parameters
-    /// \param histoSender  A shared pointer to a histogram sender
-    TrackingChain(std::shared_ptr<HistogramSender> histoSender = nullptr);
+    /// \param pManager a QA-manager
+    /// \param name A name of the task (histograms directory)
+    TrackingChain(const std::unique_ptr<cbm::algo::qa::Manager>& qaManager = nullptr, std::string_view name = "");
 
     /// \struct Input_t
     /// \brief  Input to the TrackingChain
diff --git a/algo/ca/qa/CaQa.cxx b/algo/ca/qa/CaQa.cxx
index 2184256e92..05815198dd 100644
--- a/algo/ca/qa/CaQa.cxx
+++ b/algo/ca/qa/CaQa.cxx
@@ -1,4 +1,4 @@
-/* Copyright (C) 2023-2024 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+/* Copyright (C) 2023-2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
    SPDX-License-Identifier: GPL-3.0-only
    Authors: Sergei Zharko [committer] */
 
@@ -61,7 +61,7 @@ void Qa::Init()
   using cbm::algo::qa::Prof2D;
   using fmt::format;
 
-  if (!fpSender.get()) {
+  if (!IsActive()) {
     return;
   }
 
@@ -122,26 +122,26 @@ void Qa::Init()
           auto titl = format("{} hit occupancy in XY plane for station {} ({}{});x [cm];y [cm]", setTl, iSt,
                              kDetName[detID], iStLoc);
           fvphHitOccupXY[iSt][hitSet] =
-            fQaData.MakeObj<H2D>(name, titl, nBinsXY, vMinX[iSt], vMaxX[iSt], nBinsXY, vMinY[iSt], vMaxY[iSt]);
+            MakeObj<H2D>(name, titl, nBinsXY, vMinX[iSt], vMaxX[iSt], nBinsXY, vMinY[iSt], vMaxY[iSt]);
         }
         {
           auto name = format("hit_{}_occup_zx_sta_{}", setNm, iSt);
           auto titl = format("{} hit occupancy in ZX plane for station {} ({}{});z [cm];x [cm]", setTl, iSt,
                              kDetName[detID], iStLoc);
-          fvphHitOccupZX[iSt][hitSet] = fQaData.MakeObj<H2D>(name, titl, nBinsZ, zMinA, zMaxA, nBinsXY, xMinA, xMaxA);
+          fvphHitOccupZX[iSt][hitSet] = MakeObj<H2D>(name, titl, nBinsZ, zMinA, zMaxA, nBinsXY, xMinA, xMaxA);
         }
         if (kDebug) {
           auto name = format("hit_{}_occup_zy_sta_{}", setNm, iSt);
           auto titl = format("{} hit occupancy in ZY plane for station {} ({}{});z [cm];y [cm]", setTl, iSt,
                              kDetName[detID], iStLoc);
-          fvphHitOccupZY[iSt][hitSet] = fQaData.MakeObj<H2D>(name, titl, nBinsZ, zMinA, zMaxA, nBinsXY, yMinA, yMaxA);
+          fvphHitOccupZY[iSt][hitSet] = MakeObj<H2D>(name, titl, nBinsZ, zMinA, zMaxA, nBinsXY, yMinA, yMaxA);
         }
       }
       if (kDebug) {
         auto name = format("hit_usage_xy_sta_{}", iSt);
         auto titl = format("Hit usage in XY plane for station {} ({}{});x [cm];y [cm]", iSt, kDetName[detID], iStLoc);
         fvphHitUsageXY[iSt] =
-          fQaData.MakeObj<Prof2D>(name, titl, nBinsXY, vMinX[iSt], vMaxX[iSt], nBinsXY, vMinY[iSt], vMaxY[iSt], 0., 1.);
+          MakeObj<Prof2D>(name, titl, nBinsXY, vMinX[iSt], vMaxX[iSt], nBinsXY, vMinY[iSt], vMaxY[iSt], 0., 1.);
       }
     }
     if (kDebug) {
@@ -152,12 +152,12 @@ void Qa::Init()
         {
           auto name                    = format("hit_{}_front_key_index", setNm);
           auto titl                    = format("{} hit front key index;ID_{{key}}/N_{{keys}};Count", setTl);
-          fvphHitFrontKeyIndex[hitSet] = fQaData.MakeObj<H1D>(name, titl, NBins, 0., 1.);
+          fvphHitFrontKeyIndex[hitSet] = MakeObj<H1D>(name, titl, NBins, 0., 1.);
         }
         {
           auto name                   = format("hit_{}_back_key_index", setNm);
           auto titl                   = format("{} hit back key index;ID_{{key}}/N_{{keys}};Count", setTl);
-          fvphHitBackKeyIndex[hitSet] = fQaData.MakeObj<H1D>(name, titl, NBins, 0., 1.);
+          fvphHitBackKeyIndex[hitSet] = MakeObj<H1D>(name, titl, NBins, 0., 1.);
         }
       }
     }
@@ -173,7 +173,7 @@ void Qa::Init()
           auto setTl               = EHitSet::Input == hitSet ? "Input" : "Used";
           auto name                = format("hit_{}_rel_time{}", setNm, staNm);
           auto titl                = format("{} hit relative time{}; #delta t_{{hit}};Count", setTl, staTl);
-          fvphHitTime[iSt][hitSet] = fQaData.MakeObj<H1D>(name, titl, 10000, -0.1, 1.1);
+          fvphHitTime[iSt][hitSet] = MakeObj<H1D>(name, titl, 10000, -0.1, 1.1);
         }
       }
     }
@@ -189,22 +189,22 @@ void Qa::Init()
     {
       auto sName      = format("track_{}_theta", vsPointName[i]);
       auto sTitl      = format("#theta at {} hit; #theta", vsPointName[i]);
-      fvphTrkTheta[i] = fQaData.MakeObj<H1D>(sName, sTitl, 62, 0., 90.);
+      fvphTrkTheta[i] = MakeObj<H1D>(sName, sTitl, 62, 0., 90.);
     }
     {
       auto sName    = format("track_{}_phi", vsPointName[i]);
       auto sTitl    = format("#phi at {} hit; #phi", vsPointName[i]);
-      fvphTrkPhi[i] = fQaData.MakeObj<H1D>(sName, sTitl, 62, -180., 180.);
+      fvphTrkPhi[i] = MakeObj<H1D>(sName, sTitl, 62, -180., 180.);
     }
     {
       auto sName         = format("track_{}_thata_phi", vsPointName[i]);
       auto sTitl         = format("#theta vs #phi at {} hit; #phi; #theta", vsPointName[i]);
-      fvphTrkPhiTheta[i] = fQaData.MakeObj<H2D>(sName, sTitl, 62, -180., 180., 62, 0., 90.);
+      fvphTrkPhiTheta[i] = MakeObj<H2D>(sName, sTitl, 62, -180., 180., 62, 0., 90.);
     }
     {
       auto sName        = format("track_{}_chi2_ndf", vsPointName[i]);
       auto sTitl        = format("#chi^{{2}}/NDF at {} hit; #chi^{{2}}/NDF", vsPointName[i]);
-      fvphTrkChi2Ndf[i] = fQaData.MakeObj<H1D>(sName, sTitl, 100, 0., 20.);
+      fvphTrkChi2Ndf[i] = MakeObj<H1D>(sName, sTitl, 100, 0., 20.);
     }
   }
   {
@@ -214,9 +214,9 @@ void Qa::Init()
     {
       auto sName      = "track_fst_lst_sta";
       auto sTitl      = "First vs. last station index;ID^{last}_{station};ID^{first}_{station}";
-      fphTrkFstLstSta = fQaData.MakeObj<H2D>(sName, sTitl, nBins, xMin, xMax, nBins, xMin, xMax);
+      fphTrkFstLstSta = MakeObj<H2D>(sName, sTitl, nBins, xMin, xMax, nBins, xMin, xMax);
     }
-    fphTrkNofHits = fQaData.MakeObj<H1D>("n_hits", "Number of hits;N_{hit}", nBins, xMin, xMax);
+    fphTrkNofHits = MakeObj<H1D>("n_hits", "Number of hits;N_{hit}", nBins, xMin, xMax);
   }
 
   // ---- Init canvases
@@ -235,7 +235,7 @@ void Qa::Init()
             pad.RegisterHistogram(fvphHitOccupXY[iSt][hitSet], "colz");
             canv.AddPadConfig(pad);
           }
-          fQaData.AddCanvasConfig(canv);
+          AddCanvasConfig(canv);
         }
         {  // ZX and ZY
           auto name = format("ca_hit_{}_occupancy_zx_zy", setNm);
@@ -255,7 +255,7 @@ void Qa::Init()
             }
             canv.AddPadConfig(pad);
           }
-          fQaData.AddCanvasConfig(canv);
+          AddCanvasConfig(canv);
         }
       }
       if (kDebug) {
@@ -267,7 +267,7 @@ void Qa::Init()
           pad.RegisterHistogram(fvphHitUsageXY[iSt], "colz");
           canv.AddPadConfig(pad);
         }
-        fQaData.AddCanvasConfig(canv);
+        AddCanvasConfig(canv);
       }
     }
 
@@ -294,9 +294,8 @@ void Qa::Init()
         pad.RegisterHistogram(fphTrkFstLstSta, "colz");
         canv.AddPadConfig(pad);
       }
-      fQaData.AddCanvasConfig(canv);
+      AddCanvasConfig(canv);
     }
-    fQaData.Init(fpSender);
   }
 }
 
@@ -304,7 +303,7 @@ void Qa::Init()
 //
 void Qa::Exec()
 {
-  if (!fpSender.get()) {
+  if (!IsActive()) {
     return;
   }
 
@@ -375,8 +374,6 @@ void Qa::Exec()
       trkFirstHit += nHits;
     }
   }
-
-  fQaData.Send(fpSender);
 }
 
 // ---------------------------------------------------------------------------------------------------------------------
diff --git a/algo/ca/qa/CaQa.h b/algo/ca/qa/CaQa.h
index f0c0175a3b..655d6fa50b 100644
--- a/algo/ca/qa/CaQa.h
+++ b/algo/ca/qa/CaQa.h
@@ -1,4 +1,4 @@
-/* Copyright (C) 2023 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
+/* Copyright (C) 2023-2025 GSI Helmholtzzentrum fuer Schwerionenforschung, Darmstadt
    SPDX-License-Identifier: GPL-3.0-only
    Authors: Sergei Zharko [committer] */
 
@@ -13,7 +13,7 @@
 #include "CaHit.h"  // for HitIndex_t
 #include "CaTimesliceHeader.h"
 #include "CaVector.h"
-#include "QaData.h"  // QA data
+#include "qa/QaTaskHeader.h"
 
 namespace cbm::algo
 {
@@ -21,6 +21,7 @@ namespace cbm::algo
   {
     class H1D;
     class H2D;
+    class Manager;
   }  // namespace qa
   namespace ca
   {
@@ -36,7 +37,7 @@ namespace cbm::algo::ca
   /// \class cbm::algo::ca::qa::Qa
   /// \brief Qa class for the CA tracking QA (header)
   ///
-  class Qa {
+  class Qa : public qa::TaskHeader {
     /// \brief Hit set entries
     enum class EHitSet
     {
@@ -54,8 +55,9 @@ namespace cbm::algo::ca
 
    public:
     /// \brief Default destructor
-    /// \param pSender  Pointer to the histogram sender
-    Qa(std::shared_ptr<HistogramSender> pSender) : fQaData("CA"), fpSender(pSender) {}
+    /// \param pManager  Pointer to the QA manager
+    /// \param name      Name of the QA (directory)
+    Qa(const std::unique_ptr<qa::Manager>& pManager, std::string_view name) : qa::TaskHeader(pManager, name) {}
 
     /// \brief Constructor from the configuration object
     /// \param config  QA configuration object
@@ -87,9 +89,6 @@ namespace cbm::algo::ca
     /// \return false  Some of are not initialized
     bool CheckInit() const;
 
-    /// \brief Checks, if the histogram sender is defined
-    bool IsSenderDefined() const { return static_cast<bool>(fpSender.get()); }
-
     /// \brief Registers tracking input data object
     /// \note  Call per TS
     void RegisterInputData(const InputData* pInputData) { fpInputData = pInputData; }
@@ -122,10 +121,6 @@ namespace cbm::algo::ca
     double fMinHitTime = std::numeric_limits<double>::max();
     double fMaxHitTime = std::numeric_limits<double>::lowest();
 
-    // utility
-    qa::Data fQaData;  ///< QA data
-
-    std::shared_ptr<HistogramSender> fpSender = nullptr;  ///< Histogram sender
     const Parameters<fvec>* fpParameters      = nullptr;  ///< Pointer to tracking parameters
     const InputData* fpInputData              = nullptr;  ///< Pointer to input data
     const Vector<Track>* fpvTracks            = nullptr;  ///< Pointer to tracks vector
diff --git a/algo/global/Reco.cxx b/algo/global/Reco.cxx
index 28acffb8b3..e9a0a7a9a0 100644
--- a/algo/global/Reco.cxx
+++ b/algo/global/Reco.cxx
@@ -1,6 +1,6 @@
-/* Copyright (C) 2023 FIAS Frankfurt Institute for Advanced Studies, Frankfurt / Main
+/* Copyright (C) 2023-2025 FIAS Frankfurt Institute for Advanced Studies, Frankfurt / Main
    SPDX-License-Identifier: GPL-3.0-only
-   Authors: Felix Weiglhofer [committer], P.-A. Loizeau */
+   Authors: Felix Weiglhofer [committer], P.-A. Loizeau, Sergei Zharko */
 #include "Reco.h"
 
 #include "AlgoFairloggerCompat.h"
@@ -222,7 +222,12 @@ void Reco::Init(const Options& opts)
 
   // Tracking
   if (Opts().Has(Step::Tracking)) {
-    fTracking = std::make_unique<TrackingChain>(Opts().Has(QaStep::Tracking) ? fSender : nullptr);
+    if (Opts().Has(QaStep::Tracking)) {
+      fTracking = std::make_unique<TrackingChain>(fQaManager, "CaTimeslice");
+    }
+    else {
+      fTracking = std::make_unique<TrackingChain>();
+    }
     fTracking->RegisterSetup(pTrackingSetup);
     fTracking->SetContext(&fContext);
     fTracking->Init();
diff --git a/algo/qa/QaTaskHeader.h b/algo/qa/QaTaskHeader.h
index 4038cd0d3e..ca41383152 100644
--- a/algo/qa/QaTaskHeader.h
+++ b/algo/qa/QaTaskHeader.h
@@ -22,7 +22,14 @@ namespace cbm::algo::qa
    public:
     /// \brief Constructor
     /// \param pManager a QA-manager
-    TaskHeader(const std::unique_ptr<Manager>& pManager) : fpData(pManager->GetData()) {}
+    /// \param name A name of the task (histograms directory)
+    TaskHeader(const std::unique_ptr<Manager>& pManager, std::string_view name)
+      : fpData(pManager != nullptr ? pManager->GetData() : nullptr)
+    {
+      if (fpData != nullptr) {
+        fpData->RegisterNewTask(name);
+      }
+    }
 
     /// \brief Copy constructor
     TaskHeader(const TaskHeader&) = delete;
@@ -39,6 +46,12 @@ namespace cbm::algo::qa
     /// \brief Move assignment operator
     TaskHeader& operator=(TaskHeader&&) = delete;
 
+    /// \brief Checks, if the task is active
+    ///
+    /// The task can be inactive, if a nullptr qa::Manager was passed to the constructor. If it is the case,
+    /// the fpData instance is not defined, and no actions on the task should be performed
+    bool IsActive() const { return static_cast<bool>(fpData != nullptr); }
+
    protected:
     /// \brief Adds a canvas configuration
     /// \param canvas  A CanvasConfig object
@@ -51,7 +64,7 @@ namespace cbm::algo::qa
     template<class Obj, typename... Args>
     Obj* MakeObj(Args... args)
     {
-      fpData->MakeObj<Obj>(args...);
+      return fpData->MakeObj<Obj>(args...);
     }
 
    private:
-- 
GitLab