diff --git a/services/histserv/app/Application.cxx b/services/histserv/app/Application.cxx index a391c615df5650611825e3dfbbbd44be95075a0e..a32af9d323cf450338f04bbe57700afe3d77de56 100644 --- a/services/histserv/app/Application.cxx +++ b/services/histserv/app/Application.cxx @@ -1,6 +1,6 @@ -/* Copyright (C) 2023 Facility for Antiproton and Ion Research in Europe, Darmstadt +/* Copyright (C) 2023-2024 Facility for Antiproton and Ion Research in Europe, Darmstadt SPDX-License-Identifier: GPL-3.0-only - Authors: Pierre-Alain Loizeau [committer] */ + Authors: Pierre-Alain Loizeau [committer], Sergei Zharko */ #include "Application.h" @@ -38,63 +38,69 @@ std::mutex mtx; namespace b_io = boost::iostreams; namespace b_ar = boost::archive; -namespace cbm::services::histserv -{ - // ----- Constructor --------------------------------------------------------------------------------------------- - Application::Application(ProgramOptions const& opt) : fOpt(opt) - { - /// Read options from executable - LOG(info) << "Options for Application:"; - LOG(info) << " Input ZMQ channel: " << fOpt.ComChan(); - LOG(info) << " HTTP server port: " << fOpt.HttpPort(); - if ("" != fOpt.HistoFile()) { // - LOG(info) << " Output filename: " << fOpt.HistoFile() << (fOpt.Overwrite() ? " (in overwrite mode)" : ""); - } - - /// FIXME: SOMETHING_To_Replace_FairMQ!!!!!!!!!!!!! - /// FIXME: Initialize communication channels of SOMETHING_To_Replace_FairMQ - /// FIXME: Link channel to method in order to process received messages - // fZmqSocket.set(zmq::sockopt::rcvhwm, int(hwm)); // FIXME: need for HWM? - fZmqSocket.set(zmq::sockopt::rcvtimeo, fOpt.ComChanZmqRcvTo()); // Timeout in ms to avoid stuck in loop! - fZmqSocket.bind(fOpt.ComChan().c_str()); // This side "binds" the socket => Other side should connect!!!! - - fServer = new THttpServer(Form("http:%u", fOpt.HttpPort())); - /// To avoid the server sucking all Histos from gROOT when no output file is used - fServer->GetSniffer()->SetScanGlobalDir(kFALSE); - const char* jsrootsys = gSystem->Getenv("JSROOTSYS"); - if (!jsrootsys) jsrootsys = gEnv->GetValue("HttpServ.JSRootPath", jsrootsys); - - fUiCmdActor = std::make_unique<UiCmdActor>(); - fServer->Register("/", fUiCmdActor.get()); - fServer->Hide("/UiCmdActor"); - - fServer->RegisterCommand("/Reset_Hist", "/UiCmdActor/->SetResetHistos()"); - fServer->RegisterCommand("/Save_Hist", "/UiCmdActor/->SetSaveHistos()"); - fServer->RegisterCommand("/Stop_Server", "/UiCmdActor/->SetServerStop()"); - - /* - fServer->RegisterCommand("/Reset_Hist", "this->ResetHistograms()"); - fServer->RegisterCommand("/Save_Hist", "this->SaveHistograms()"); - */ - - fServer->Restrict("/Reset_Hist", "allow=admin"); - fServer->Restrict("/Save_Hist", "allow=admin"); - fServer->Restrict("/Stop_Server", "allow=admin"); +using cbm::services::histserv::Application; - - LOG(info) << "JSROOT location: " << jsrootsys; +// --------------------------------------------------------------------------------------------------------------------- +// +Application::Application(ProgramOptions const& opt) : fOpt(opt) +{ + /// Read options from executable + LOG(info) << "Options for Application:"; + LOG(info) << " Input ZMQ channel: " << fOpt.ComChan(); + LOG(info) << " HTTP server port: " << fOpt.HttpPort(); + if ("" != fOpt.HistoFile()) { // + LOG(info) << " Output filename: " << fOpt.HistoFile() << (fOpt.Overwrite() ? " (in overwrite mode)" : ""); } - // ------------------------------------------------------------------------------------------------------------------- - - // ----- Main Loop ----------------------------------------------------------------------------------------------- - void Application::Exec() - { - fStopThread = false; - fThread = std::thread(&Application::UpdateHttpServer, this); - while (!(fUiCmdActor->GetServerStop())) { // + /// FIXME: SOMETHING_To_Replace_FairMQ!!!!!!!!!!!!! + /// FIXME: Initialize communication channels of SOMETHING_To_Replace_FairMQ + /// FIXME: Link channel to method in order to process received messages + // fZmqSocket.set(zmq::sockopt::rcvhwm, int(hwm)); // FIXME: need for HWM? + fZmqSocket.set(zmq::sockopt::rcvtimeo, fOpt.ComChanZmqRcvTo()); // Timeout in ms to avoid stuck in loop! + fZmqSocket.bind(fOpt.ComChan().c_str()); // This side "binds" the socket => Other side should connect!!!! + + fServer = new THttpServer(Form("http:%u", fOpt.HttpPort())); + /// To avoid the server sucking all Histos from gROOT when no output file is used + fServer->GetSniffer()->SetScanGlobalDir(kFALSE); + const char* jsrootsys = gSystem->Getenv("JSROOTSYS"); + if (!jsrootsys) jsrootsys = gEnv->GetValue("HttpServ.JSRootPath", jsrootsys); + + fUiCmdActor = std::make_unique<UiCmdActor>(); + fServer->Register("/", fUiCmdActor.get()); + fServer->Hide("/UiCmdActor"); + + fServer->RegisterCommand("/Reset_Hist", "/UiCmdActor/->SetResetHistos()"); + fServer->RegisterCommand("/Save_Hist", "/UiCmdActor/->SetSaveHistos()"); + fServer->RegisterCommand("/Stop_Server", "/UiCmdActor/->SetServerStop()"); + + /* + fServer->RegisterCommand("/Reset_Hist", "this->ResetHistograms()"); + fServer->RegisterCommand("/Save_Hist", "this->SaveHistograms()"); + */ + + fServer->Restrict("/Reset_Hist", "allow=admin"); + fServer->Restrict("/Save_Hist", "allow=admin"); + fServer->Restrict("/Stop_Server", "allow=admin"); + + // Provide signal handling for external interruptions + // NOTE: SZh 02.04.2024: + // This function is needed for ZMQ to throw an exception (zmq::error_t) on the interrupt signal. It seems, that + // the actual body of the function does not matter. This behaviour is not understood. + signal(SIGINT, [](int) {}); + + LOG(info) << "JSROOT location: " << jsrootsys; +} + +// --------------------------------------------------------------------------------------------------------------------- +// +void Application::Exec() +{ + fStopThread = false; + fThread = std::thread(&Application::UpdateHttpServer, this); + LOG(info) << "Listening to ZMQ messages ..."; + while (!(fUiCmdActor->GetServerStop())) { // + try { /// Infinite loop, this is a service which should survive until told otherwise after all - /// FIXME: Start listening to <SOMETHING?!?> to receive histograms and configuration /// FIXME: handle signals from OS/console /* Jan suggestion with zmq_addon CPP interface */ @@ -113,96 +119,153 @@ namespace cbm::services::histserv LOG(error) << "Invalid number of message parts received: should be either 1 or more than 3 vs " << *ret; } } + catch (const zmq::error_t& err) { + if (err.num() == EINTR) { + // FIXME: SZh: Are the socket and the context finished properly? + LOG(info) << "Histogram server execution was interrupted by user. Finishing application"; + break; + } + else { + throw err; + } + } } - // ------------------------------------------------------------------------------------------------------------------- +} - // ----- Constructor --------------------------------------------------------------------------------------------- - Application::~Application() - { - SaveHistograms(); - fStopThread = true; - fThread.join(); - SaveHistograms(); +// --------------------------------------------------------------------------------------------------------------------- +// +Application::~Application() +{ + SaveHistograms(); + fStopThread = true; + fThread.join(); + SaveHistograms(); +} + +// --------------------------------------------------------------------------------------------------------------------- +// +void Application::UpdateHttpServer() +{ + /// This is needed to have a reactive GUI independently of histogram updates reception + while (!fStopThread) { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + std::lock_guard<std::mutex> lk(mtx); + + fServer->ProcessRequests(); + + /// TODO: control flags communication from histo server to histograms sources? + /// Idea: 1 req channel (per process or not, mixup?), polling every N TS and/or M s + if (fUiCmdActor->GetResetHistos()) { + LOG(info) << "Reset Monitor histos "; + ResetHistograms(); + fUiCmdActor->SetResetHistos(false); + } // if( fUiCmdActor->GetResetHistos() ) + + if (fUiCmdActor->GetSaveHistos()) { + LOG(info) << "Save All histos & canvases"; + SaveHistograms(); + fUiCmdActor->SetSaveHistos(false); + } // if( fUiCmdActor->GetSaveHistos() ) } - // ------------------------------------------------------------------------------------------------------------------- - - // ----- Server update background thread ------------------------------------------------------------------------- - void Application::UpdateHttpServer() - { - /// This is needed to have a reactive GUI independently of histogram updates reception - while (!fStopThread) { - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - std::lock_guard<std::mutex> lk(mtx); +} - fServer->ProcessRequests(); - - /// TODO: control flags communication from histo server to histograms sources? - /// Idea: 1 req channel (per process or not, mixup?), polling every N TS and/or M s - if (fUiCmdActor->GetResetHistos()) { - LOG(info) << "Reset Monitor histos "; - ResetHistograms(); - fUiCmdActor->SetResetHistos(false); - } // if( fUiCmdActor->GetResetHistos() ) - - if (fUiCmdActor->GetSaveHistos()) { - LOG(info) << "Save All histos & canvases"; - SaveHistograms(); - fUiCmdActor->SetSaveHistos(false); - } // if( fUiCmdActor->GetSaveHistos() ) +// --------------------------------------------------------------------------------------------------------------------- +// +bool Application::ReceiveData(zmq::message_t& msg) +{ + LOG(debug) << "Application::ReceiveData => Processing histograms update"; + + /// FIXME: Something to replace FairMQ and extract the histograms!!!! + /// FIXME: Need something to replace the ROOT serializer which allowed to have any of TH1x, TH2x, TH3x or TProfile + /// FIXME: Need something to replace the TObjArray which allowed to have a mix of of TH1x, TH2x, TH3x or TProfile + b_io::basic_array_source<char> device(static_cast<char*>(msg.data()), msg.size()); + b_io::stream<b_io::basic_array_source<char>> s(device); + b_ar::binary_iarchive iarch(s); + + cbm::algo::qa::HistogramContainer vHist; + iarch >> vHist; + + /// copied from CbmTaskDigiEventQa::ToTH1D + /// FIXME: Should be placed in a tools/interface/whatever library with all similar functions!! + /// FIXME: Reverse OP need to be implemented + CI unit tests for back and forth in each direction (ROOT <-> Algo) + /// 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; } - // ------------------------------------------------------------------------------------------------------------------- - - // ----- Server update background thread ------------------------------------------------------------------------- - bool Application::ReceiveData(zmq::message_t& msg) - { - LOG(debug) << "Application::ReceiveData => Processing histograms update"; - - /// FIXME: Something to replace FairMQ and extract the histograms!!!! - /// FIXME: Need something to replace the ROOT serializer which allowed to have any of TH1x, TH2x, TH3x or TProfile - /// FIXME: Need something to replace the TObjArray which allowed to have a mix of of TH1x, TH2x, TH3x or TProfile - b_io::basic_array_source<char> device(static_cast<char*>(msg.data()), msg.size()); - b_io::stream<b_io::basic_array_source<char>> s(device); - b_ar::binary_iarchive iarch(s); - - cbm::algo::qa::HistogramContainer vHist; - iarch >> vHist; - - /// copied from CbmTaskDigiEventQa::ToTH1D - /// FIXME: Should be placed in a tools/interface/whatever library with all similar functions!! - /// FIXME: Reverse OP need to be implemented + CI unit tests for back and forth in each direction (ROOT <-> Algo) - /// 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; } - for (auto& source : vHist.fvH2) { - TH1* result = cbm::qa::OnlineInterface::ROOTHistogram(source); - if (!ReadHistogram<TH1>(result)) { // - return false; - } - delete result; + delete result; + } + for (auto& source : vHist.fvP1) { + TH1* result = cbm::qa::OnlineInterface::ROOTHistogram(source); + if (!ReadHistogram<TH1>(result)) { // + return false; } - for (auto& source : vHist.fvP1) { - TH1* result = cbm::qa::OnlineInterface::ROOTHistogram(source); - if (!ReadHistogram<TH1>(result)) { // - return false; - } - delete result; + delete result; + } + for (auto& source : vHist.fvP2) { + TH1* result = cbm::qa::OnlineInterface::ROOTHistogram(source); + if (!ReadHistogram<TH1>(result)) { // + return false; } - for (auto& source : vHist.fvP2) { - TH1* result = cbm::qa::OnlineInterface::ROOTHistogram(source); - if (!ReadHistogram<TH1>(result)) { // - return false; + delete result; + } + + + /// If new histos received, try to prepare as many canvases as possible + /// Should be expensive on start and cheap afterward + if (!fbAllCanvasReady) { + LOG(debug) << "Application::ReceiveData => Checking for canvases updates"; + for (uint32_t uCanv = 0; uCanv < fvpsCanvasConfig.size(); ++uCanv) { + /// Jump canvases already ready + if (fvbCanvasReady[uCanv]) { // + continue; } - delete result; - } + /// Now come the expensive part as we unpack its config and check each histo + fvbCanvasReady[uCanv] = PrepareCanvas(uCanv); + } // for( uint32_t uCanv = 0; uCanv < fvpsCanvasConfig.size(); ++uCanv ) + } // if( !fbAllCanvasReady ) + + /* + TObject* tempObject = nullptr; + if (TString(tempObject->ClassName()).EqualTo("TObjArray")) { + std::lock_guard<std::mutex> lk(mtx); + TObjArray* arrayHisto = static_cast<TObjArray*>(tempObject); + for (Int_t i = 0; i < arrayHisto->GetEntriesFast(); i++) { + TObject* pObj = arrayHisto->At(i); + + if (nullptr != dynamic_cast<TProfile*>(pObj)) { + if (!ReadHistogram<TProfile>(dynamic_cast<TProfile*>(pObj))) { // + return false; + } + } // if( nullptr != dynamic_cast< TProfile *>( pObj ) ) + else if (nullptr != dynamic_cast<TH2*>(pObj)) { + if (!ReadHistogram<TH2>(dynamic_cast<TH2*>(pObj))) { // + return false; + } + } // if( nullptr != dynamic_cast< TH2 *>( pObj ) ) + else if (nullptr != dynamic_cast<TH1*>(pObj)) { + if (!ReadHistogram<TH1>(dynamic_cast<TH1*>(pObj))) { // + return false; + } + } // if( nullptr != dynamic_cast< TH1 *>( pObj ) ) + else + LOG(warning) << "Unsupported object type for " << pObj->GetName(); + } // for (Int_t i = 0; i < arrayHisto->GetEntriesFast(); i++) + + LOG(debug) << "Application::ReceiveData => Deleting array"; + /// Need to use Delete instead of Clear to avoid memory leak!!! + arrayHisto->Delete(); /// If new histos received, try to prepare as many canvases as possible /// Should be expensive on start and cheap afterward @@ -218,453 +281,419 @@ namespace cbm::services::histserv fvbCanvasReady[uCanv] = PrepareCanvas(uCanv); } // for( uint32_t uCanv = 0; uCanv < fvpsCanvasConfig.size(); ++uCanv ) } // if( !fbAllCanvasReady ) + } // if (TString(tempObject->ClassName()).EqualTo("TObjArray")) + else { + fStopThread = true; + std::string err_msg = "Application::ReceiveData => Wrong object type at input: "; + err_msg += tempObject->ClassName(); + throw std::runtime_error(err_msg); + } - /* - TObject* tempObject = nullptr; - if (TString(tempObject->ClassName()).EqualTo("TObjArray")) { - std::lock_guard<std::mutex> lk(mtx); - TObjArray* arrayHisto = static_cast<TObjArray*>(tempObject); - for (Int_t i = 0; i < arrayHisto->GetEntriesFast(); i++) { - TObject* pObj = arrayHisto->At(i); - - if (nullptr != dynamic_cast<TProfile*>(pObj)) { - if (!ReadHistogram<TProfile>(dynamic_cast<TProfile*>(pObj))) { // - return false; - } - } // if( nullptr != dynamic_cast< TProfile *>( pObj ) ) - else if (nullptr != dynamic_cast<TH2*>(pObj)) { - if (!ReadHistogram<TH2>(dynamic_cast<TH2*>(pObj))) { // - return false; - } - } // if( nullptr != dynamic_cast< TH2 *>( pObj ) ) - else if (nullptr != dynamic_cast<TH1*>(pObj)) { - if (!ReadHistogram<TH1>(dynamic_cast<TH1*>(pObj))) { // - return false; - } - } // if( nullptr != dynamic_cast< TH1 *>( pObj ) ) - else - LOG(warning) << "Unsupported object type for " << pObj->GetName(); - } // for (Int_t i = 0; i < arrayHisto->GetEntriesFast(); i++) - - LOG(debug) << "Application::ReceiveData => Deleting array"; - /// Need to use Delete instead of Clear to avoid memory leak!!! - arrayHisto->Delete(); - - /// If new histos received, try to prepare as many canvases as possible - /// Should be expensive on start and cheap afterward - if (!fbAllCanvasReady) { - LOG(debug) << "Application::ReceiveData => Checking for canvases updates"; - for (uint32_t uCanv = 0; uCanv < fvpsCanvasConfig.size(); ++uCanv) { - /// Jump canvases already ready - if (fvbCanvasReady[uCanv]) { // - continue; - } - - /// Now come the expensive part as we unpack its config and check each histo - fvbCanvasReady[uCanv] = PrepareCanvas(uCanv); - } // for( uint32_t uCanv = 0; uCanv < fvpsCanvasConfig.size(); ++uCanv ) - } // if( !fbAllCanvasReady ) - } // if (TString(tempObject->ClassName()).EqualTo("TObjArray")) - else { - fStopThread = true; - std::string err_msg = "Application::ReceiveData => Wrong object type at input: "; - err_msg += tempObject->ClassName(); - throw std::runtime_error(err_msg); - } + if (nullptr != tempObject) delete tempObject; + */ - if (nullptr != tempObject) delete tempObject; - */ + fNMessages += 1; - fNMessages += 1; + LOG(debug) << "Application::ReceiveData => Finished processing histograms update"; - LOG(debug) << "Application::ReceiveData => Finished processing histograms update"; + return true; +} - return true; +// --------------------------------------------------------------------------------------------------------------------- +// +bool Application::ReceiveHistoConfig(zmq::message_t& msg) +{ + /// 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()); + b_io::stream<b_io::basic_array_source<char>> s(device); + b_ar::binary_iarchive iarch(s); + + std::pair<std::string, std::string> tempObject("", ""); + iarch >> tempObject; + + LOG(info) << " 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(info) << " 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; + } // else of if( uPrevHist < fvpsHistosFolder.size() ) + + return true; +} + +// --------------------------------------------------------------------------------------------------------------------- +// +bool Application::ReceiveCanvasConfig(zmq::message_t& msg) +{ + /// 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()); + b_io::stream<b_io::basic_array_source<char>> s(device); + b_ar::binary_iarchive iarch(s); + + std::pair<std::string, std::string> tempObject("", ""); + iarch >> tempObject; + + LOG(info) << " Received configuration for canvas " << tempObject.first << " : " << tempObject.second; + + /// Check if canvas name already received in previous messages + /// Linear search should be ok as config is shared only at startup + uint32_t uPrevCanv = 0; + for (uPrevCanv = 0; uPrevCanv < fvpsCanvasConfig.size(); ++uPrevCanv) { + if (fvpsCanvasConfig[uPrevCanv].first == tempObject.first) { // + break; + } + } // for( UInt_t uPrevCanv = 0; uPrevCanv < fvpsCanvasConfig.size(); ++uPrevCanv ) + + if (uPrevCanv < fvpsCanvasConfig.size()) { + LOG(warning) << " Ignored new configuration for Canvas " << tempObject.first + << " due to previously received one: " << tempObject.second; + /// Not sure if we should return false here... + } // if( uPrevCanv < fvpsCanvasConfig.size() ) + else { + fvpsCanvasConfig.push_back(tempObject); + fvbCanvasReady.push_back(false); + fbAllCanvasReady = false; + + fvCanvas.push_back(std::pair<TCanvas*, std::string>(nullptr, "")); + fvbCanvasRegistered.push_back(false); + fbAllCanvasRegistered = false; + } // else of if( uPrevCanv < fvpsCanvasConfig.size() ) + return true; +} + +// --------------------------------------------------------------------------------------------------------------------- +// +bool Application::ReceiveConfigAndData(std::vector<zmq::message_t>& vMsg) +{ + /// FIXME: Something to replace FairMQ and extract the histograms!!!! + LOG(debug) << "Application::ReceiveConfigAndData => Received composed message with " << vMsg.size() << " parts"; + + /// Header contains a pair of unsigned integers + /// FIXME: Something to replace FairMQ and extract the header!!!! + // BoostSerializer<std::pair<uint32_t, uint32_t>>().Deserialize(vMsg.at(0), pairHeader); + b_io::basic_array_source<char> device_header(static_cast<char*>(vMsg.at(0).data()), vMsg.at(0).size()); + b_io::stream<b_io::basic_array_source<char>> s_header(device_header); + b_ar::binary_iarchive iarch_header(s_header); + + std::pair<uint32_t, uint32_t> pairHeader; + iarch_header >> pairHeader; + LOG(debug) << "Application::ReceiveConfigAndData => Received configuration for " << pairHeader.first << " histos and " + << pairHeader.second << " canvases"; + + uint32_t uOffsetHistoConfig = pairHeader.first; + if (0 == pairHeader.first) { + uOffsetHistoConfig = 1; + if (0 < vMsg[uOffsetHistoConfig].size()) { + fStopThread = true; + fUiCmdActor->SetServerStop(); + std::string err_msg = "Application::ReceiveConfigAndData => No histo config expected but corresponding message"; + err_msg += " is not empty: "; + err_msg += vMsg[uOffsetHistoConfig].size(); + throw std::runtime_error(err_msg); + } } - bool Application::ReceiveHistoConfig(zmq::message_t& msg) - { - /// 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()); - b_io::stream<b_io::basic_array_source<char>> s(device); - b_ar::binary_iarchive iarch(s); - - std::pair<std::string, std::string> tempObject("", ""); - iarch >> tempObject; - - LOG(info) << " Received configuration for histo " << tempObject.first << " : " << tempObject.second; + uint32_t uOffsetCanvasConfig = pairHeader.second; + if (0 == pairHeader.second) { + uOffsetCanvasConfig = 1; + if (0 < vMsg[uOffsetHistoConfig + uOffsetCanvasConfig].size()) { + fStopThread = true; + fUiCmdActor->SetServerStop(); + std::string err_msg = "Application::ReceiveConfigAndData => No Canvas config expected but corresponding "; + err_msg += " message is not empty: "; + err_msg += vMsg[uOffsetHistoConfig + uOffsetCanvasConfig].size(); + throw std::runtime_error(err_msg); + } + } - /// 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(info) << " 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; - } // else of if( uPrevHist < fvpsHistosFolder.size() ) - - return true; + if ((1 + uOffsetHistoConfig + uOffsetCanvasConfig + 1) != vMsg.size()) { + fStopThread = true; + fUiCmdActor->SetServerStop(); + std::string err_msg = "Application::ReceiveConfigAndData => Nb parts in message not matching configs numbers "; + err_msg += " declared in header"; + err_msg += vMsg.size(); + err_msg += " VS "; + err_msg += 1 + uOffsetHistoConfig + uOffsetCanvasConfig + 1; + throw std::runtime_error(err_msg); } - bool Application::ReceiveCanvasConfig(zmq::message_t& msg) - { - /// 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()); - b_io::stream<b_io::basic_array_source<char>> s(device); - b_ar::binary_iarchive iarch(s); + /// Decode parts for histograms configuration (auto-skip empty message if 0 declared in header) + for (uint32_t uHisto = 0; uHisto < pairHeader.first; ++uHisto) { + ReceiveHistoConfig(vMsg[1 + uHisto]); + } // for (UInt_t uHisto = 0; uHisto < pairHeader.first; ++uHisto) + LOG(debug) << "Application::ReceiveConfigAndData => Processed configuration for " << pairHeader.first << " histos"; - std::pair<std::string, std::string> tempObject("", ""); - iarch >> tempObject; + /// Decode parts for histograms configuration (auto-skip empty message if 0 declared in header) + for (uint32_t uCanv = 0; uCanv < pairHeader.second; ++uCanv) { + ReceiveCanvasConfig(vMsg[1 + uOffsetHistoConfig + uCanv]); + } // for (UInt_t uCanv = 0; uCanv < pairHeader.second; ++uCanv) + LOG(debug) << "Application::ReceiveConfigAndData => Processed configuration for " << pairHeader.second << " canvases"; - LOG(info) << " Received configuration for canvas " << tempObject.first << " : " << tempObject.second; + /// Decode the histograms data now that the configuration is loaded + ReceiveData(vMsg[1 + uOffsetHistoConfig + uOffsetCanvasConfig]); - /// Check if canvas name already received in previous messages - /// Linear search should be ok as config is shared only at startup - uint32_t uPrevCanv = 0; - for (uPrevCanv = 0; uPrevCanv < fvpsCanvasConfig.size(); ++uPrevCanv) { - if (fvpsCanvasConfig[uPrevCanv].first == tempObject.first) { // - break; - } - } // for( UInt_t uPrevCanv = 0; uPrevCanv < fvpsCanvasConfig.size(); ++uPrevCanv ) - - if (uPrevCanv < fvpsCanvasConfig.size()) { - LOG(warning) << " Ignored new configuration for Canvas " << tempObject.first - << " due to previously received one: " << tempObject.second; - /// Not sure if we should return false here... - } // if( uPrevCanv < fvpsCanvasConfig.size() ) - else { - fvpsCanvasConfig.push_back(tempObject); - fvbCanvasReady.push_back(false); - fbAllCanvasReady = false; - - fvCanvas.push_back(std::pair<TCanvas*, std::string>(nullptr, "")); - fvbCanvasRegistered.push_back(false); - fbAllCanvasRegistered = false; - } // else of if( uPrevCanv < fvpsCanvasConfig.size() ) - return true; - } + return true; +} - bool Application::ReceiveConfigAndData(std::vector<zmq::message_t>& vMsg) - { - /// FIXME: Something to replace FairMQ and extract the histograms!!!! - LOG(debug) << "Application::ReceiveConfigAndData => Received composed message with " << vMsg.size() << " parts"; - - /// Header contains a pair of unsigned integers - /// FIXME: Something to replace FairMQ and extract the header!!!! - // BoostSerializer<std::pair<uint32_t, uint32_t>>().Deserialize(vMsg.at(0), pairHeader); - b_io::basic_array_source<char> device_header(static_cast<char*>(vMsg.at(0).data()), vMsg.at(0).size()); - b_io::stream<b_io::basic_array_source<char>> s_header(device_header); - b_ar::binary_iarchive iarch_header(s_header); - - std::pair<uint32_t, uint32_t> pairHeader; - iarch_header >> pairHeader; - LOG(debug) << "Application::ReceiveConfigAndData => Received configuration for " << pairHeader.first - << " histos and " << pairHeader.second << " canvases"; - - uint32_t uOffsetHistoConfig = pairHeader.first; - if (0 == pairHeader.first) { - uOffsetHistoConfig = 1; - if (0 < vMsg[uOffsetHistoConfig].size()) { - fStopThread = true; - fUiCmdActor->SetServerStop(); - std::string err_msg = "Application::ReceiveConfigAndData => No histo config expected but corresponding message"; - err_msg += " is not empty: "; - err_msg += vMsg[uOffsetHistoConfig].size(); - throw std::runtime_error(err_msg); - } - } +// --------------------------------------------------------------------------------------------------------------------- +// +template<class HistoT> +bool Application::ReadHistogram(HistoT* pHist) +{ + int index1 = FindHistogram(pHist->GetName()); + if (-1 == index1) { + HistoT* histogram_new = static_cast<HistoT*>(pHist->Clone()); + fArrayHisto.Add(histogram_new); + + LOG(info) << "Received new histo " << pHist->GetName(); + + /// If new histo received, try to register it if configuration available + if (!fbAllHistosRegistered) { + for (uint32_t uHist = 0; uHist < fvpsHistosFolder.size(); ++uHist) { + /// Jump histos already ready + if (fvbHistoRegistered[uHist]) { // + continue; + } - uint32_t uOffsetCanvasConfig = pairHeader.second; - if (0 == pairHeader.second) { - uOffsetCanvasConfig = 1; - if (0 < vMsg[uOffsetHistoConfig + uOffsetCanvasConfig].size()) { - fStopThread = true; - fUiCmdActor->SetServerStop(); - std::string err_msg = "Application::ReceiveConfigAndData => No Canvas config expected but corresponding "; - err_msg += " message is not empty: "; - err_msg += vMsg[uOffsetHistoConfig + uOffsetCanvasConfig].size(); - throw std::runtime_error(err_msg); - } - } + /// Check if name matches one in config for others + if (fvpsHistosFolder[uHist].first == histogram_new->GetName()) { + fvHistos[uHist] = std::pair<TNamed*, std::string>(histogram_new, fvpsHistosFolder[uHist].second); + fServer->Register(Form("/%s", fvHistos[uHist].second.data()), fvHistos[uHist].first); + fvbHistoRegistered[uHist] = true; + + LOG(info) << "registered histo " << fvHistos[uHist].first->GetName() << " in folder " + << fvHistos[uHist].second; + + + /// Update flag telling whether all known histos are registered + fbAllHistosRegistered = true; + for (uint32_t uIdx = 0; uIdx < fvbHistoRegistered.size(); ++uIdx) { + if (!fvbHistoRegistered[uIdx]) { + fbAllHistosRegistered = false; + break; + } // if( !fvbHistoRegistered[ uIdx ] ) + } // for( uint32_t uIdx = 0; uIdx < fvbHistoRegistered.size(); ++uIdx ) + + break; + } // if( fvpsHistosFolder[ uHist ].first == histogram_new->GetName() ) + } // for( uint32_t uCanv = 0; uCanv < fvpsCanvasConfig.size(); ++uCanv ) + } // if( !fbAllCanvasReady ) + } // if (-1 == index1) + else { + LOG(debug) << "Received update for: " << pHist->GetName(); + HistoT* histogram_existing = dynamic_cast<HistoT*>(fArrayHisto.At(index1)); + if (nullptr == histogram_existing) { + LOG(error) << "CbmMqHistoServer::ReadHistogram => " + << "Incompatible type found during update for histo " << pHist->GetName(); + return false; + } // if( nullptr == histogram_existing ) - if ((1 + uOffsetHistoConfig + uOffsetCanvasConfig + 1) != vMsg.size()) { - fStopThread = true; - fUiCmdActor->SetServerStop(); - std::string err_msg = "Application::ReceiveConfigAndData => Nb parts in message not matching configs numbers "; - err_msg += " declared in header"; - err_msg += vMsg.size(); - err_msg += " VS "; - err_msg += 1 + uOffsetHistoConfig + uOffsetCanvasConfig + 1; - throw std::runtime_error(err_msg); - } + histogram_existing->Add(pHist); + } // else of if (-1 == index1) + return true; +} - /// Decode parts for histograms configuration (auto-skip empty message if 0 declared in header) - for (uint32_t uHisto = 0; uHisto < pairHeader.first; ++uHisto) { - ReceiveHistoConfig(vMsg[1 + uHisto]); - } // for (UInt_t uHisto = 0; uHisto < pairHeader.first; ++uHisto) - LOG(debug) << "Application::ReceiveConfigAndData => Processed configuration for " << pairHeader.first << " histos"; +// --------------------------------------------------------------------------------------------------------------------- +// +int Application::FindHistogram(const std::string& name) +{ + for (int iHist = 0; iHist < fArrayHisto.GetEntriesFast(); ++iHist) { + TObject* obj = fArrayHisto.At(iHist); + if (TString(obj->GetName()).EqualTo(name)) { // + return iHist; + } + } // for( int iHist = 0; iHist < fArrayHisto.GetEntriesFast(); ++iHist ) + return -1; +} - /// Decode parts for histograms configuration (auto-skip empty message if 0 declared in header) - for (uint32_t uCanv = 0; uCanv < pairHeader.second; ++uCanv) { - ReceiveCanvasConfig(vMsg[1 + uOffsetHistoConfig + uCanv]); - } // for (UInt_t uCanv = 0; uCanv < pairHeader.second; ++uCanv) - LOG(debug) << "Application::ReceiveConfigAndData => Processed configuration for " << pairHeader.second - << " canvases"; +// --------------------------------------------------------------------------------------------------------------------- +// +bool Application::PrepareCanvas(uint32_t uCanvIdx) +{ + LOG(info) << " Extracting configuration for canvas index " << uCanvIdx; + CanvasConfig conf(ExtractCanvasConfigFromString(fvpsCanvasConfig[uCanvIdx].second)); + + /// First check if all objects to be drawn are present + uint32_t uNbPads(conf.GetNbPads()); + for (uint32_t uPadIdx = 0; uPadIdx < uNbPads; ++uPadIdx) { + uint32_t uNbObj(conf.GetNbObjsInPad(uPadIdx)); + for (uint32_t uObjIdx = 0; uObjIdx < uNbObj; ++uObjIdx) { + std::string sName(conf.GetObjName(uPadIdx, uObjIdx)); + /// Check for empty pads! + if ("nullptr" != sName) { + if (FindHistogram(sName) < 0) { + return false; + } // if( FindHistogram( conf.GetObjName( uPadIdx, uObjIdx ) ) < 0 ) + } // if( "nullptr" != sName ) + } // for( uint32_t uObjIdx = 0; uObjIdx < uNbObj; ++uObjIdx ) + } // for( uint32_t uPadIdx = 0; uPadIdx < uNbPads; ++uPadIdx ) + + LOG(info) << " All histos found for canvas " << conf.GetName().data() << ", now preparing it"; + + // Temporary solution to save canvases into directories + std::string sNameFull = conf.GetName(); + size_t lastSlashPos = sNameFull.find_last_of('/'); + std::string sNamePart = lastSlashPos > sNameFull.size() ? sNameFull : sNameFull.substr(lastSlashPos + 1); + std::string sDir = lastSlashPos > sNameFull.size() ? "" : sNameFull.substr(0, lastSlashPos); + std::string canvDir = sDir.empty() ? "canvases" : fmt::format("canvases/{}", sDir); + + /// Create new canvas and pads + TCanvas* pNewCanv = new TCanvas(sNamePart.c_str(), conf.GetTitle().data()); + pNewCanv->Divide(conf.GetNbPadsX(), conf.GetNbPadsY()); + + /// Loop on pads + for (uint32_t uPadIdx = 0; uPadIdx < uNbPads; ++uPadIdx) { + pNewCanv->cd(1 + uPadIdx); + + /// Pad settings + gPad->SetGrid(conf.GetGridx(uPadIdx), conf.GetGridy(uPadIdx)); + gPad->SetLogx(conf.GetLogx(uPadIdx)); + gPad->SetLogy(conf.GetLogy(uPadIdx)); + gPad->SetLogz(conf.GetLogz(uPadIdx)); + + /// Add objects (we know they are there + uint32_t uNbObj(conf.GetNbObjsInPad(uPadIdx)); + for (uint32_t uObjIdx = 0; uObjIdx < uNbObj; ++uObjIdx) { + std::string sName(conf.GetObjName(uPadIdx, uObjIdx)); + if ("nullptr" != sName) { + TObject* pObj = fArrayHisto[FindHistogram(sName)]; + + if (nullptr != dynamic_cast<TProfile2D*>(pObj)) { + dynamic_cast<TProfile2D*>(pObj)->Draw(conf.GetOption(uPadIdx, uObjIdx).data()); + } // if( nullptr != dynamic_cast< TProfile *>( pObj ) ) + else if (nullptr != dynamic_cast<TProfile*>(pObj)) { + dynamic_cast<TProfile*>(pObj)->Draw(conf.GetOption(uPadIdx, uObjIdx).data()); + } // if( nullptr != dynamic_cast< TProfile *>( pObj ) ) + else if (nullptr != dynamic_cast<TH2*>(pObj)) { + dynamic_cast<TH2*>(pObj)->Draw(conf.GetOption(uPadIdx, uObjIdx).data()); + } // if( nullptr != dynamic_cast< TH2 *>( pObj ) ) + else if (nullptr != dynamic_cast<TH1*>(pObj)) { + dynamic_cast<TH1*>(pObj)->Draw(conf.GetOption(uPadIdx, uObjIdx).data()); + } // if( nullptr != dynamic_cast< TH1 *>( pObj ) ) + else + LOG(warning) << " Unsupported object type for " << sName << " when preparing canvas " << conf.GetName(); - /// Decode the histograms data now that the configuration is loaded - ReceiveData(vMsg[1 + uOffsetHistoConfig + uOffsetCanvasConfig]); + LOG(info) << " Configured histo " << sName << " on pad " << (1 + uPadIdx) << " for canvas " + << conf.GetName().data(); + } // if( "nullptr" != sName ) + } // for( uint32_t uObjIdx = 0; uObjIdx < uNbObj; ++uObjIdx ) + } // for( uint32_t uPadIdx = 0; uPadIdx < uNbPads; ++uPadIdx ) - return true; - } - // ------------------------------------------------------------------------------------------------------------------- - - template<class HistoT> - bool Application::ReadHistogram(HistoT* pHist) - { - int index1 = FindHistogram(pHist->GetName()); - if (-1 == index1) { - HistoT* histogram_new = static_cast<HistoT*>(pHist->Clone()); - fArrayHisto.Add(histogram_new); - - LOG(info) << "Received new histo " << pHist->GetName(); - - /// If new histo received, try to register it if configuration available - if (!fbAllHistosRegistered) { - for (uint32_t uHist = 0; uHist < fvpsHistosFolder.size(); ++uHist) { - /// Jump histos already ready - if (fvbHistoRegistered[uHist]) { // - continue; - } - - /// Check if name matches one in config for others - if (fvpsHistosFolder[uHist].first == histogram_new->GetName()) { - fvHistos[uHist] = std::pair<TNamed*, std::string>(histogram_new, fvpsHistosFolder[uHist].second); - fServer->Register(Form("/%s", fvHistos[uHist].second.data()), fvHistos[uHist].first); - fvbHistoRegistered[uHist] = true; - - LOG(info) << "registered histo " << fvHistos[uHist].first->GetName() << " in folder " - << fvHistos[uHist].second; - - - /// Update flag telling whether all known histos are registered - fbAllHistosRegistered = true; - for (uint32_t uIdx = 0; uIdx < fvbHistoRegistered.size(); ++uIdx) { - if (!fvbHistoRegistered[uIdx]) { - fbAllHistosRegistered = false; - break; - } // if( !fvbHistoRegistered[ uIdx ] ) - } // for( uint32_t uIdx = 0; uIdx < fvbHistoRegistered.size(); ++uIdx ) - - break; - } // if( fvpsHistosFolder[ uHist ].first == histogram_new->GetName() ) - } // for( uint32_t uCanv = 0; uCanv < fvpsCanvasConfig.size(); ++uCanv ) - } // if( !fbAllCanvasReady ) - } // if (-1 == index1) - else { - LOG(debug) << "Received update for: " << pHist->GetName(); - HistoT* histogram_existing = dynamic_cast<HistoT*>(fArrayHisto.At(index1)); - if (nullptr == histogram_existing) { - LOG(error) << "CbmMqHistoServer::ReadHistogram => " - << "Incompatible type found during update for histo " << pHist->GetName(); - return false; - } // if( nullptr == histogram_existing ) - - histogram_existing->Add(pHist); - } // else of if (-1 == index1) - return true; - } + fvCanvas[uCanvIdx] = std::pair<TCanvas*, std::string>(pNewCanv, canvDir); + fServer->Register(Form("/%s", fvCanvas[uCanvIdx].second.data()), fvCanvas[uCanvIdx].first); + fvbCanvasRegistered[uCanvIdx] = true; - int Application::FindHistogram(const std::string& name) - { - for (int iHist = 0; iHist < fArrayHisto.GetEntriesFast(); ++iHist) { - TObject* obj = fArrayHisto.At(iHist); - if (TString(obj->GetName()).EqualTo(name)) { // - return iHist; - } - } // for( int iHist = 0; iHist < fArrayHisto.GetEntriesFast(); ++iHist ) - return -1; - } + LOG(info) << " Registered canvas " << fvCanvas[uCanvIdx].first->GetName() << " in folder " + << fvCanvas[uCanvIdx].second; - bool Application::PrepareCanvas(uint32_t uCanvIdx) - { - LOG(info) << " Extracting configuration for canvas index " << uCanvIdx; - CanvasConfig conf(ExtractCanvasConfigFromString(fvpsCanvasConfig[uCanvIdx].second)); - - /// First check if all objects to be drawn are present - uint32_t uNbPads(conf.GetNbPads()); - for (uint32_t uPadIdx = 0; uPadIdx < uNbPads; ++uPadIdx) { - uint32_t uNbObj(conf.GetNbObjsInPad(uPadIdx)); - for (uint32_t uObjIdx = 0; uObjIdx < uNbObj; ++uObjIdx) { - std::string sName(conf.GetObjName(uPadIdx, uObjIdx)); - /// Check for empty pads! - if ("nullptr" != sName) { - if (FindHistogram(sName) < 0) { - return false; - } // if( FindHistogram( conf.GetObjName( uPadIdx, uObjIdx ) ) < 0 ) - } // if( "nullptr" != sName ) - } // for( uint32_t uObjIdx = 0; uObjIdx < uNbObj; ++uObjIdx ) - } // for( uint32_t uPadIdx = 0; uPadIdx < uNbPads; ++uPadIdx ) - - LOG(info) << " All histos found for canvas " << conf.GetName().data() << ", now preparing it"; - - // Temporary solution to save canvases into directories - std::string sNameFull = conf.GetName(); - size_t lastSlashPos = sNameFull.find_last_of('/'); - std::string sNamePart = lastSlashPos > sNameFull.size() ? sNameFull : sNameFull.substr(lastSlashPos + 1); - std::string sDir = lastSlashPos > sNameFull.size() ? "" : sNameFull.substr(0, lastSlashPos); - std::string canvDir = sDir.empty() ? "canvases" : fmt::format("canvases/{}", sDir); - - /// Create new canvas and pads - TCanvas* pNewCanv = new TCanvas(sNamePart.c_str(), conf.GetTitle().data()); - pNewCanv->Divide(conf.GetNbPadsX(), conf.GetNbPadsY()); - - /// Loop on pads - for (uint32_t uPadIdx = 0; uPadIdx < uNbPads; ++uPadIdx) { - pNewCanv->cd(1 + uPadIdx); - - /// Pad settings - gPad->SetGrid(conf.GetGridx(uPadIdx), conf.GetGridy(uPadIdx)); - gPad->SetLogx(conf.GetLogx(uPadIdx)); - gPad->SetLogy(conf.GetLogy(uPadIdx)); - gPad->SetLogz(conf.GetLogz(uPadIdx)); - - /// Add objects (we know they are there - uint32_t uNbObj(conf.GetNbObjsInPad(uPadIdx)); - for (uint32_t uObjIdx = 0; uObjIdx < uNbObj; ++uObjIdx) { - std::string sName(conf.GetObjName(uPadIdx, uObjIdx)); - if ("nullptr" != sName) { - TObject* pObj = fArrayHisto[FindHistogram(sName)]; - - if (nullptr != dynamic_cast<TProfile2D*>(pObj)) { - dynamic_cast<TProfile2D*>(pObj)->Draw(conf.GetOption(uPadIdx, uObjIdx).data()); - } // if( nullptr != dynamic_cast< TProfile *>( pObj ) ) - else if (nullptr != dynamic_cast<TProfile*>(pObj)) { - dynamic_cast<TProfile*>(pObj)->Draw(conf.GetOption(uPadIdx, uObjIdx).data()); - } // if( nullptr != dynamic_cast< TProfile *>( pObj ) ) - else if (nullptr != dynamic_cast<TH2*>(pObj)) { - dynamic_cast<TH2*>(pObj)->Draw(conf.GetOption(uPadIdx, uObjIdx).data()); - } // if( nullptr != dynamic_cast< TH2 *>( pObj ) ) - else if (nullptr != dynamic_cast<TH1*>(pObj)) { - dynamic_cast<TH1*>(pObj)->Draw(conf.GetOption(uPadIdx, uObjIdx).data()); - } // if( nullptr != dynamic_cast< TH1 *>( pObj ) ) - else - LOG(warning) << " Unsupported object type for " << sName << " when preparing canvas " << conf.GetName(); - - LOG(info) << " Configured histo " << sName << " on pad " << (1 + uPadIdx) << " for canvas " - << conf.GetName().data(); - } // if( "nullptr" != sName ) - } // for( uint32_t uObjIdx = 0; uObjIdx < uNbObj; ++uObjIdx ) - } // for( uint32_t uPadIdx = 0; uPadIdx < uNbPads; ++uPadIdx ) - - fvCanvas[uCanvIdx] = std::pair<TCanvas*, std::string>(pNewCanv, canvDir); - fServer->Register(Form("/%s", fvCanvas[uCanvIdx].second.data()), fvCanvas[uCanvIdx].first); - fvbCanvasRegistered[uCanvIdx] = true; - - LOG(info) << " Registered canvas " << fvCanvas[uCanvIdx].first->GetName() << " in folder " - << fvCanvas[uCanvIdx].second; - - /// Update flag telling whether all known canvases are registered - fbAllCanvasRegistered = true; - for (uint32_t uIdx = 0; uIdx < fvbCanvasRegistered.size(); ++uIdx) { - if (!fvbCanvasRegistered[uIdx]) { - fbAllCanvasRegistered = false; - break; - } // if( !fvbCanvasRegistered[ uIdx ] ) - } // for( uint32_t uIdx = 0; uIdx < fvbCanvasRegistered.size(); ++uIdx ) + /// Update flag telling whether all known canvases are registered + fbAllCanvasRegistered = true; + for (uint32_t uIdx = 0; uIdx < fvbCanvasRegistered.size(); ++uIdx) { + if (!fvbCanvasRegistered[uIdx]) { + fbAllCanvasRegistered = false; + break; + } // if( !fvbCanvasRegistered[ uIdx ] ) + } // for( uint32_t uIdx = 0; uIdx < fvbCanvasRegistered.size(); ++uIdx ) - return true; - } + return true; +} - bool Application::ResetHistograms() - { - for (int iHist = 0; iHist < fArrayHisto.GetEntriesFast(); ++iHist) { - dynamic_cast<TH1*>(fArrayHisto.At(iHist))->Reset(); - } // for( int iHist = 0; iHist < fArrayHisto.GetEntriesFast(); ++iHist ) - return true; +// --------------------------------------------------------------------------------------------------------------------- +// +bool Application::ResetHistograms() +{ + for (int iHist = 0; iHist < fArrayHisto.GetEntriesFast(); ++iHist) { + dynamic_cast<TH1*>(fArrayHisto.At(iHist))->Reset(); + } // for( int iHist = 0; iHist < fArrayHisto.GetEntriesFast(); ++iHist ) + return true; +} + +// --------------------------------------------------------------------------------------------------------------------- +// +bool Application::SaveHistograms() +{ + if ("" == fOpt.HistoFile()) { // + LOG(error) << "Filename for saving histograms and canvases not defined. Ignoring request."; + return false; } - bool Application::SaveHistograms() - { - if ("" == fOpt.HistoFile()) { // - LOG(error) << "Filename for saving histograms and canvases not defined. Ignoring request."; - return false; - } + /// Save old global file and folder pointer to avoid messing with FairRoot + TFile* oldFile = gFile; + TDirectory* oldDir = gDirectory; - /// Save old global file and folder pointer to avoid messing with FairRoot - TFile* oldFile = gFile; - TDirectory* oldDir = gDirectory; + /// (Re-)Create ROOT file to store the histos + TFile* histoFile = nullptr; - /// (Re-)Create ROOT file to store the histos - TFile* histoFile = nullptr; + // open separate histo file in recreate mode + histoFile = new TFile(fOpt.HistoFile().data(), fOpt.Overwrite() ? "RECREATE" : "CREATE"); - // open separate histo file in recreate mode - histoFile = new TFile(fOpt.HistoFile().data(), fOpt.Overwrite() ? "RECREATE" : "CREATE"); - - if (nullptr == histoFile) { // - gFile = oldFile; - gDirectory = oldDir; - LOG(error) << "Ignoring request to save histograms and canvases: could not open output file " << fOpt.HistoFile(); - return false; - } - - LOG(info) << "Saving Histograms and canvases in file: " << fOpt.HistoFile(); + if (nullptr == histoFile) { // + gFile = oldFile; + gDirectory = oldDir; + LOG(error) << "Ignoring request to save histograms and canvases: could not open output file " << fOpt.HistoFile(); + return false; + } - /// Register the histos in the HTTP server - for (UInt_t uHisto = 0; uHisto < fvHistos.size(); ++uHisto) { - /// catch case of histograms declared in config but not yet received - if (nullptr != fvHistos[uHisto].first) { - /// Make sure we end up in chosen folder - TString sFolder = fvHistos[uHisto].second.data(); - if (nullptr == gDirectory->Get(sFolder)) { // - gDirectory->mkdir(sFolder); - } - gDirectory->cd(sFolder); + LOG(info) << "Saving Histograms and canvases in file: " << fOpt.HistoFile(); - /// Write plot - fvHistos[uHisto].first->Write(); + /// Register the histos in the HTTP server + for (UInt_t uHisto = 0; uHisto < fvHistos.size(); ++uHisto) { + /// catch case of histograms declared in config but not yet received + if (nullptr != fvHistos[uHisto].first) { + /// Make sure we end up in chosen folder + TString sFolder = fvHistos[uHisto].second.data(); + if (nullptr == gDirectory->Get(sFolder)) { // + gDirectory->mkdir(sFolder); } + gDirectory->cd(sFolder); - histoFile->cd(); - } // for( UInt_t uHisto = 0; uHisto < fvHistos.size(); ++uHisto ) + /// Write plot + fvHistos[uHisto].first->Write(); + } - for (UInt_t uCanvas = 0; uCanvas < fvCanvas.size(); ++uCanvas) { - /// catch case of canvases declared in config but for which not all histos were yet received - if (nullptr != fvCanvas[uCanvas].first) { - /// Make sure we end up in chosen folder - TString sFolder = fvCanvas[uCanvas].second.data(); - if (nullptr == gDirectory->Get(sFolder)) { // - gDirectory->mkdir(sFolder); - } - gDirectory->cd(sFolder); + histoFile->cd(); + } // for( UInt_t uHisto = 0; uHisto < fvHistos.size(); ++uHisto ) - /// Write plot - fvCanvas[uCanvas].first->Write(); + for (UInt_t uCanvas = 0; uCanvas < fvCanvas.size(); ++uCanvas) { + /// catch case of canvases declared in config but for which not all histos were yet received + if (nullptr != fvCanvas[uCanvas].first) { + /// Make sure we end up in chosen folder + TString sFolder = fvCanvas[uCanvas].second.data(); + if (nullptr == gDirectory->Get(sFolder)) { // + gDirectory->mkdir(sFolder); } + gDirectory->cd(sFolder); - histoFile->cd(); - } // for( UInt_t uHisto = 0; uHisto < fvCanvas.size(); ++uHisto ) + /// Write plot + fvCanvas[uCanvas].first->Write(); + } - /// Restore old global file and folder pointer to avoid messing with FairRoot - gFile = oldFile; - gDirectory = oldDir; + histoFile->cd(); + } // for( UInt_t uHisto = 0; uHisto < fvCanvas.size(); ++uHisto ) - histoFile->Close(); + /// Restore old global file and folder pointer to avoid messing with FairRoot + gFile = oldFile; + gDirectory = oldDir; - return true; - } + histoFile->Close(); -} // namespace cbm::services::histserv + return true; +} diff --git a/services/histserv/app/Application.h b/services/histserv/app/Application.h index a4f41f10090a16b549da49dfad59a04ef222db63..69ee11b6797c8143cef418d8bfd35d919c3555b6 100644 --- a/services/histserv/app/Application.h +++ b/services/histserv/app/Application.h @@ -5,17 +5,17 @@ #ifndef CBM_SERVICES_HISTSERV_APP_APPLICATION_H #define CBM_SERVICES_HISTSERV_APP_APPLICATION_H 1 +#include "ProgramOptions.h" #include "THttpServer.h" #include "TObjArray.h" +#include "ui_callbacks.h" +#include <csignal> #include <memory> #include <string> #include <thread> #include <zmq.hpp> -#include "ProgramOptions.h" -#include "ui_callbacks.h" - class TCanvas; class TNamed; @@ -44,18 +44,43 @@ namespace cbm::services::histserv private: //const std::string& ConfigFile() const; - bool ReceiveData(zmq::message_t& msg); - bool ReceiveHistoConfig(zmq::message_t& msg); - bool ReceiveCanvasConfig(zmq::message_t& msg); - bool ReceiveConfigAndData(std::vector<zmq::message_t>& vMsg); + /// \brief Receives histograms + bool ReceiveData(zmq::message_t& msg); + + /// \brief Receives histogram configuration + bool ReceiveHistoConfig(zmq::message_t& msg); + + /// \brief Receives canvas configuration + bool ReceiveCanvasConfig(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 Read a histogram + /// \tparam HistoT Histogram type + /// \param pHist Pointer to the histogram + template<class HistoT> + bool ReadHistogram(HistoT* pHist); + + /// \brief Find histogram index in the histogram array + /// \param name A name of the histogram + int FindHistogram(const std::string& name); + + /// \brief Prepares canvases using received canvas configuration + /// \param uCanvIdx Index of canvas + bool PrepareCanvas(uint32_t uCanvIdx); + + /// \brief Resets handled histograms + bool ResetHistograms(); + + /// \brief Saves handled histograms + bool SaveHistograms(); - template<class HistoT> - bool ReadHistogram(HistoT* pHist); - int FindHistogram(const std::string& name); - bool PrepareCanvas(uint32_t uCanvIdx); + /// \brief A handler for system signals + /// \param signal Signal ID + //static void SignalHandler(int signal); - bool ResetHistograms(); - bool SaveHistograms(); private: ProgramOptions const& fOpt; ///< Program options object @@ -74,7 +99,7 @@ namespace cbm::services::histserv /// Vector of string with ( HistoName, FolderPath ) to configure the histogram std::vector<std::pair<std::string, std::string>> fvpsHistosFolder = {}; /// Vector of string pairs with ( CanvasName, CanvasConfig ) to configure the canvases and histos within - /// Format of Can config is "Name;Title;NbPadX(U);NbPadY(U);ConfigPad1(s);....;ConfigPadXY(s)" + /// Format of Can config is "Name;Title;NbPadX(U);NbPadY(U);ConfigPad2(s);....;ConfigPadXY(s)" /// Format of Pad config is "GrixX(b),GridY(b),LogX(b),LogY(b),LogZ(b),HistoName(s),DrawOptions(s)" std::vector<std::pair<std::string, std::string>> fvpsCanvasConfig = {}; std::vector<bool> fvbCanvasReady = {};