From 8d2df157a76e7ad9191d8475f6fd9c2bbeb8cfdb Mon Sep 17 00:00:00 2001
From: Felix Weiglhofer <weiglhofer@fias.uni-frankfurt.de>
Date: Thu, 16 Nov 2023 15:57:06 +0000
Subject: [PATCH] Add archive explorer to display online archives.

---
 algo/global/StorableRecoResults.h             |   4 +
 services/CMakeLists.txt                       |   1 +
 services/archive_explorer/CMakeLists.txt      |  53 +++++++
 services/archive_explorer/app/Application.cxx |  31 ++++
 services/archive_explorer/app/Application.h   |  30 ++++
 services/archive_explorer/app/Options.cxx     |  53 +++++++
 services/archive_explorer/app/Options.h       |  20 +++
 services/archive_explorer/app/main.cxx        |  20 +++
 services/archive_explorer/lib/LinkDef.h       |   8 +
 services/archive_explorer/lib/Server.cxx      | 149 ++++++++++++++++++
 services/archive_explorer/lib/Server.h        |  71 +++++++++
 11 files changed, 440 insertions(+)
 create mode 100644 services/archive_explorer/CMakeLists.txt
 create mode 100644 services/archive_explorer/app/Application.cxx
 create mode 100644 services/archive_explorer/app/Application.h
 create mode 100644 services/archive_explorer/app/Options.cxx
 create mode 100644 services/archive_explorer/app/Options.h
 create mode 100644 services/archive_explorer/app/main.cxx
 create mode 100644 services/archive_explorer/lib/LinkDef.h
 create mode 100644 services/archive_explorer/lib/Server.cxx
 create mode 100644 services/archive_explorer/lib/Server.h

diff --git a/algo/global/StorableRecoResults.h b/algo/global/StorableRecoResults.h
index dcf4d80dde..87d99fc8b3 100644
--- a/algo/global/StorableRecoResults.h
+++ b/algo/global/StorableRecoResults.h
@@ -6,7 +6,10 @@
 
 #include "CbmDigiEvent.h"
 
+#include "tof/Hit.h"
+
 #include <boost/serialization/access.hpp>
+#include <boost/serialization/utility.hpp>
 #include <boost/serialization/vector.hpp>
 
 #include <cstdint>
@@ -14,6 +17,7 @@
 #include "PartitionedVector.h"
 #include "ca/core/data/CaTrack.h"
 #include "ca/core/utils/CaVector.h"
+#include "sts/Hit.h"
 
 namespace cbm::algo
 {
diff --git a/services/CMakeLists.txt b/services/CMakeLists.txt
index e34c0786ba..7ccb2f7d10 100644
--- a/services/CMakeLists.txt
+++ b/services/CMakeLists.txt
@@ -1 +1,2 @@
+add_subdirectory(archive_explorer)
 add_subdirectory(histserv)
diff --git a/services/archive_explorer/CMakeLists.txt b/services/archive_explorer/CMakeLists.txt
new file mode 100644
index 0000000000..7631ccca32
--- /dev/null
+++ b/services/archive_explorer/CMakeLists.txt
@@ -0,0 +1,53 @@
+#
+# Application
+#
+set(APP cbm-archive-explorer)
+
+set(APP_SRCS
+  app/Application.cxx
+  app/Options.cxx
+  app/main.cxx
+)
+
+add_executable(${APP} ${APP_SRCS})
+
+target_link_libraries(${APP}
+  Algo
+  Boost::program_options
+  external::fles_logging
+  fmt::fmt
+)
+
+#
+# libCbmArchiveExplorer
+#
+
+set(LIBRARY_NAME CbmArchiveExplorer)
+set(LINKDEF lib/LinkDef.h)
+
+set(INCLUDE_DIRECTORIES
+  lib
+)
+
+set(SRCS
+  lib/Server.cxx
+)
+
+set(HEADERS
+  lib/Server.h
+)
+
+set(PUBLIC_DEPENDENCIES
+  Algo
+  external::fles_logging
+  fmt::fmt
+  ROOT::Core
+  ROOT::Gpad
+  ROOT::Hist
+  ROOT::RIO
+  ROOT::RHTTP
+)
+
+generate_cbm_library()
+
+target_link_libraries(${APP} CbmArchiveExplorer)
diff --git a/services/archive_explorer/app/Application.cxx b/services/archive_explorer/app/Application.cxx
new file mode 100644
index 0000000000..c3cc8e17e2
--- /dev/null
+++ b/services/archive_explorer/app/Application.cxx
@@ -0,0 +1,31 @@
+/* Copyright (C) 2023 FIAS Frankfurt Institute for Advanced Studies, Frankfurt / Main
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Felix Weiglhofer [committer] */
+#include "Application.h"
+
+#include <log.hpp>
+
+#include "RecoResultsInputArchive.h"
+#include "Server.h"
+
+using namespace cbm::explore;
+using namespace cbm::algo;
+
+Application::Application(const Options& options) : fOpts(options)
+{
+  L_(info) << "Starting application with port " << fOpts.Port();
+
+  L_(info) << "Opening archive " << fOpts.Input();
+
+  auto archive = std::make_shared<RecoResultsInputArchive>(fOpts.Input().string());
+
+  fServer = std::make_unique<Server>(fOpts.Port(), archive);
+}
+
+Application::~Application() {}
+
+int Application::Run()
+{
+  fServer->Run();
+  return 0;
+}
diff --git a/services/archive_explorer/app/Application.h b/services/archive_explorer/app/Application.h
new file mode 100644
index 0000000000..49b418f221
--- /dev/null
+++ b/services/archive_explorer/app/Application.h
@@ -0,0 +1,30 @@
+/* Copyright (C) 2023 FIAS Frankfurt Institute for Advanced Studies, Frankfurt / Main
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Felix Weiglhofer [committer] */
+#pragma once
+
+#include <memory>
+
+#include "Options.h"
+
+namespace cbm::explore
+{
+
+  class Server;
+
+  class Application {
+
+  public:
+    Application(const Options& options);
+
+    ~Application();
+
+    int Run();
+
+  private:
+    Options fOpts;
+
+    std::unique_ptr<Server> fServer;
+  };
+
+}  // namespace cbm::explore
diff --git a/services/archive_explorer/app/Options.cxx b/services/archive_explorer/app/Options.cxx
new file mode 100644
index 0000000000..9520cc81bd
--- /dev/null
+++ b/services/archive_explorer/app/Options.cxx
@@ -0,0 +1,53 @@
+/* Copyright (C) 2023 FIAS Frankfurt Institute for Advanced Studies, Frankfurt / Main
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Felix Weiglhofer [committer] */
+#include "Options.h"
+
+#include <boost/program_options.hpp>
+
+#include <iostream>
+
+namespace po = boost::program_options;
+
+Options::Options(int argc, char** argv)
+{
+  po::options_description options("Options");
+
+  // clang-format off
+  options.add_options()
+    ("input,i", po::value(&fInput)->value_name("<input>")->required(),
+      "input archive")
+    ("port,p", po::value(&fPort)->value_name("<port>")->default_value(8080),
+      "port to listen on")
+    ("help,h",
+      "produce help message")
+  ;
+  // clang-format on
+
+  po::variables_map vm;
+  po::command_line_parser parser {argc, argv};
+  parser.options(options);
+  try {
+    auto result = parser.run();
+    po::store(result, vm);
+  }
+  catch (const std::exception& e) {
+    std::cerr << "Error: " << e.what() << std::endl;
+    std::cerr << "Use '-h' to display all valid options." << std::endl;
+    std::exit(EXIT_FAILURE);
+  }
+
+  if (vm.count("help") > 0) {
+    std::cout << options << std::endl;
+    std::exit(EXIT_SUCCESS);
+  }
+
+  try {
+    po::notify(vm);
+  }
+  catch (const po::required_option& e) {
+    std::cerr << "Error: " << e.what() << std::endl;
+    std::cerr << "Use '-h' to display all valid options." << std::endl;
+    std::exit(EXIT_FAILURE);
+  }
+}
diff --git a/services/archive_explorer/app/Options.h b/services/archive_explorer/app/Options.h
new file mode 100644
index 0000000000..9ec407b889
--- /dev/null
+++ b/services/archive_explorer/app/Options.h
@@ -0,0 +1,20 @@
+/* Copyright (C) 2023 FIAS Frankfurt Institute for Advanced Studies, Frankfurt / Main
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Felix Weiglhofer [committer] */
+#pragma once
+
+#include <compat/Filesystem.h>
+
+class Options {
+public:
+  Options(int argc, char** argv);
+
+  int Port() const { return fPort; }
+
+  cbm::algo::fs::path Input() const { return fInput; }
+
+
+private:
+  int fPort;
+  std::string fInput;
+};
diff --git a/services/archive_explorer/app/main.cxx b/services/archive_explorer/app/main.cxx
new file mode 100644
index 0000000000..e0a206f2d3
--- /dev/null
+++ b/services/archive_explorer/app/main.cxx
@@ -0,0 +1,20 @@
+/* Copyright (C) 2023 FIAS Frankfurt Institute for Advanced Studies, Frankfurt / Main
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Felix Weiglhofer [committer] */
+
+#include <log.hpp>
+
+#include "Application.h"
+#include "Options.h"
+
+using namespace cbm::explore;
+
+int main(int argc, char** argv)
+{
+
+  Options options(argc, argv);
+
+  Application app(options);
+
+  return app.Run();
+}
diff --git a/services/archive_explorer/lib/LinkDef.h b/services/archive_explorer/lib/LinkDef.h
new file mode 100644
index 0000000000..1e8c4b81af
--- /dev/null
+++ b/services/archive_explorer/lib/LinkDef.h
@@ -0,0 +1,8 @@
+/* Copyright (C) 2023 FIAS Frankfurt Institute for Advanced Studies, Frankfurt / Main
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Felix Weiglhofer [committer] */
+#pragma link off all globals;
+#pragma link off all classes;
+#pragma link off all functions;
+
+#pragma link C++ class cbm::explore::Server + ;
diff --git a/services/archive_explorer/lib/Server.cxx b/services/archive_explorer/lib/Server.cxx
new file mode 100644
index 0000000000..6ddb0950a2
--- /dev/null
+++ b/services/archive_explorer/lib/Server.cxx
@@ -0,0 +1,149 @@
+/* Copyright (C) 2023 FIAS Frankfurt Institute for Advanced Studies, Frankfurt / Main
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Felix Weiglhofer [committer] */
+#include "Server.h"
+
+#include <TH1.h>
+#include <THttpServer.h>
+#include <TSystem.h>
+
+#include <log.hpp>
+
+#include <fmt/format.h>
+
+using namespace cbm::explore;
+
+ClassImp(Server);
+
+Server::Server(int port, std::shared_ptr<algo::RecoResultsInputArchive> archive)
+  : TNamed("server", "server")
+  , fArchive(archive)
+{
+  L_(info) << "Starting Server with port " << port;
+
+  fServer = new THttpServer(fmt::format("http:{}", port).c_str());
+  fServer->SetTimer(0, true);
+
+  // FIXME: Why doesn't root use the local jsroot by default???
+  fServer->SetJSROOT("https://jsroot.gsi.de/7.1.0/");
+}
+
+Server::~Server() {}
+
+int Server::Run()
+{
+  // register this object in http server
+  fServer->Register("/", this);
+  fServer->Hide("/server");
+
+  // enable monitoring and
+  // specify items to draw when page is opened
+  // fServer->SetItemField("/", "_monitoring", "5000");
+
+  // Specify layout. TODO: does not work?
+  fServer->SetItemField("/", "_layout", "grid3x3");
+
+  fServer->RegisterCommand("/NextTS", "/server/->RequestNextTS()");
+  fServer->SetItemField("/NextTS", "_title", "Next Timeslice");
+
+  CreateHistos();
+  NextTS();
+
+  L_(info) << "Starting event loop";
+  while (true) {
+
+    int nRequests = 0;
+    do {
+      nRequests = fServer->ProcessRequests();
+      if (nRequests > 0) L_(info) << "Processed " << nRequests << " requests";
+
+      // sleep minimal time
+      // TODO: This is terrible. And the sleep should not be in this loop.
+      // But ROOTJS sometimes sends commands multiple times,
+      // And this is the only way, i was able to catch duplicate commands
+      gSystem->Sleep(SleepPerTick_ms);
+    } while (nRequests > 0);
+
+    if (fRequestNextTS) {
+      NextTS();
+      fRequestNextTS = false;
+    }
+  }
+
+  return 0;
+}
+
+void Server::NextTS()
+{
+  L_(info) << "Fetching next Timeslice...";
+
+  auto recoResults = fArchive->get();
+
+  if (fArchive->eos()) {
+    L_(info) << "End of archive reached";
+    return;
+  }
+
+  ResetHistos();
+
+  auto& stsHits = recoResults->StsHits();
+  for (size_t p = 0; p < stsHits.NPartitions(); p++) {
+    auto [sensor, address] = stsHits.Partition(p);
+    L_(info) << "Filling sensor " << address << " with " << sensor.size() << " hits";
+    for (auto& hit : sensor) {
+      fHStsHitsX->Fill(hit.X());
+      fHStsHitsY->Fill(hit.Y());
+      fHStsHitsZ->Fill(hit.Z());
+      fHStsHitsTime->Fill(hit.Time());
+      fHStsHitsDx->Fill(hit.Dx());
+      fHStsHitsDy->Fill(hit.Dy());
+      fHStsHitsDz->Fill(hit.fDz);
+      fHStsHitsDxy->Fill(hit.fDxy);
+      fHStsHitsTimeError->Fill(hit.TimeError());
+      fHStsHitsDu->Fill(hit.fDu);
+      fHStsHitsDv->Fill(hit.fDv);
+    }
+  }
+
+  L_(info) << "Finished Timeslice";
+}
+
+void Server::ResetHistos()
+{
+  for (auto* histo : fHistos) {
+    histo->Reset();
+  }
+}
+
+void Server::CreateHistos()
+{
+  CreateFolder("/sts", "STS");
+  CreateFolder("/sts/hits", "Hits");
+  CreateHisto(fHStsHitsX, "/sts/hits", "hStsHitsX", "Sts Hits X", 100, -10, 10);
+  CreateHisto(fHStsHitsY, "/sts/hits", "hStsHitsY", "Sts Hits Y", 100, -10, 10);
+  CreateHisto(fHStsHitsZ, "/sts/hits", "hStsHitsZ", "Sts Hits Z", 100, -10, 10);
+  CreateHisto(fHStsHitsTime, "/sts/hits", "hStsHitsTime", "Sts Hits Time", 1000, 0, 128000000);
+  CreateHisto(fHStsHitsDx, "/sts/hits", "hStsHitsDx", "Sts Hits Dx", 100, 0, 0.1);
+  CreateHisto(fHStsHitsDy, "/sts/hits", "hStsHitsDy", "Sts Hits Dy", 100, 0, 0.1);
+  CreateHisto(fHStsHitsDz, "/sts/hits", "hStsHitsDz", "Sts Hits Dz", 100, 0, 0.1);
+  CreateHisto(fHStsHitsDxy, "/sts/hits", "hStsHitsDxy", "Sts Hits Dxy", 100, 0, 0.1);
+  CreateHisto(fHStsHitsTimeError, "/sts/hits", "hStsHitsTimeError", "Sts Hits Time Error", 100, 0, 0.1);
+  CreateHisto(fHStsHitsDu, "/sts/hits", "hStsHitsDu", "Sts Hits Du", 100, 0, 0.1);
+  CreateHisto(fHStsHitsDv, "/sts/hits", "hStsHitsDv", "Sts Hits Dv", 100, 0, 0.1);
+}
+
+template<typename Histo_t>
+void Server::CreateHisto(Histo_t*& histo, const char* folder, const char* name, const char* title, int nbins,
+                         double xlow, double xmax)
+{
+  histo = new Histo_t(name, title, nbins, xlow, xmax);
+  histo->SetDirectory(nullptr);
+  fServer->Register(folder, histo);
+  fHistos.push_back(histo);
+}
+
+void Server::CreateFolder(const char* path, const char* name)
+{
+  fServer->CreateItem(path, name);
+  fServer->SetItemField(path, "_kind", "Folder");
+}
diff --git a/services/archive_explorer/lib/Server.h b/services/archive_explorer/lib/Server.h
new file mode 100644
index 0000000000..d20c7e3e8a
--- /dev/null
+++ b/services/archive_explorer/lib/Server.h
@@ -0,0 +1,71 @@
+/* Copyright (C) 2023 FIAS Frankfurt Institute for Advanced Studies, Frankfurt / Main
+   SPDX-License-Identifier: GPL-3.0-only
+   Authors: Felix Weiglhofer [committer] */
+#pragma once
+
+#include <Rtypes.h>
+#include <TNamed.h>
+
+#include <memory>
+
+#include "RecoResultsInputArchive.h"
+
+class THttpServer;
+class TH1;
+class TH1F;
+class TH1I;
+
+namespace cbm::explore
+{
+
+  class Server : public TNamed {
+
+  public:
+    Server(int port, std::shared_ptr<algo::RecoResultsInputArchive> archive);
+
+    virtual ~Server();
+
+    int Run();
+
+    // Server commands
+    void RequestNextTS() { fRequestNextTS = true; }
+
+  private:
+    static constexpr int SleepPerTick_ms = 50;
+
+    // Internal state
+    THttpServer* fServer = nullptr;                           //!
+    std::shared_ptr<algo::RecoResultsInputArchive> fArchive;  //!
+    std::vector<TH1*> fHistos;                                //!
+
+    // Server commands
+    bool fRequestNextTS = false;
+
+    // Histograms
+    TH1F* fHStsHitsX         = nullptr;
+    TH1F* fHStsHitsY         = nullptr;
+    TH1F* fHStsHitsZ         = nullptr;
+    TH1I* fHStsHitsTime      = nullptr;
+    TH1F* fHStsHitsDx        = nullptr;
+    TH1F* fHStsHitsDy        = nullptr;
+    TH1F* fHStsHitsDz        = nullptr;
+    TH1F* fHStsHitsDxy       = nullptr;
+    TH1F* fHStsHitsTimeError = nullptr;
+    TH1F* fHStsHitsDu        = nullptr;
+    TH1F* fHStsHitsDv        = nullptr;
+
+    void NextTS();
+    void ResetHistos();
+
+    void CreateHistos();
+
+    template<typename Histo_t>
+    void CreateHisto(Histo_t*& histo, const char* folder, const char* name, const char* title, int nbins, double xlow,
+                     double xmax);
+
+    void CreateFolder(const char* path, const char* name);
+
+    ClassDef(Server, 1);
+  };
+
+}  // namespace cbm::explore
-- 
GitLab