diff --git a/Common/Utils/include/CommonUtils/ConfigurableParam.h b/Common/Utils/include/CommonUtils/ConfigurableParam.h index 0a6da30aa39f5..fa4acd2744703 100644 --- a/Common/Utils/include/CommonUtils/ConfigurableParam.h +++ b/Common/Utils/include/CommonUtils/ConfigurableParam.h @@ -407,6 +407,8 @@ class ConfigurableParam // writes a human readable INI or JSON file depending on the extension static void write(std::string const& filename, std::string const& keyOnly = ""); + static std::string asJSON(std::string const& keyOnly = ""); + // can be used instead of using API on concrete child classes template static T getValueAs(std::string key) @@ -494,6 +496,9 @@ class ConfigurableParam // be updated, absence of data for any of requested params will lead to fatal static void updateFromFile(std::string const&, std::string const& paramsList = "", bool unchangedOnly = false); + // update from a JSON string with the same filtering semantics as updateFromFile + static void updateFromJSONString(std::string const&, std::string const& paramsList = "", bool unchangedOnly = false); + // interface for use from the CCDB API; allows to sync objects read from CCDB with the information // stored in the registry; modifies given object as well as registry virtual void syncCCDBandRegistry(void* obj) = 0; diff --git a/Common/Utils/src/ConfigurableParam.cxx b/Common/Utils/src/ConfigurableParam.cxx index 202433b04e4c4..4de478a6d0e9f 100644 --- a/Common/Utils/src/ConfigurableParam.cxx +++ b/Common/Utils/src/ConfigurableParam.cxx @@ -37,6 +37,7 @@ #endif #include #include +#include #include #include #include @@ -754,6 +755,29 @@ void ConfigurableParam::writeJSON(std::string const& filename, std::string const // ------------------------------------------------------------------ +std::string ConfigurableParam::asJSON(std::string const& keyOnly) +{ + initPropertyTree(); // update the boost tree before writing + std::ostringstream os; + if (!keyOnly.empty()) { // write ini for selected key only + try { + boost::property_tree::ptree kTree; + auto keys = o2::utils::Str::tokenize(keyOnly, " ,;", true, true); + for (const auto& k : keys) { + kTree.add_child(k, sPtree->get_child(k)); + } + boost::property_tree::write_json(os, kTree); + } catch (const boost::property_tree::ptree_bad_path& err) { + LOG(fatal) << "non-existing key " << keyOnly << " provided to writeJSON"; + } + } else { + boost::property_tree::write_json(os, *sPtree); + } + return os.str(); +} + +// ------------------------------------------------------------------ + void ConfigurableParam::initPropertyTree() { sPtree->clear(); @@ -870,26 +894,10 @@ void ConfigurableParam::printAllRegisteredParamNames() // ------------------------------------------------------------------ -// Update the storage map of params from the given configuration file. -// It can be in JSON or INI format. -// If nonempty comma-separated paramsList is provided, only those params will -// be updated, absence of data for any of requested params will lead to fatal -// If unchangedOnly is true, then only those parameters whose provenance is kCODE will be updated -// (to allow preference of run-time settings) -void ConfigurableParam::updateFromFile(std::string const& configFile, std::string const& paramsList, bool unchangedOnly) +namespace +{ +void updateFromPropertyTree(boost::property_tree::ptree const& pt, std::string const& source, std::string const& paramsList, bool unchangedOnly) { - if (!sIsFullyInitialized) { - initialize(); - } - - auto cfgfile = o2::utils::Str::trim_copy(configFile); - - if (cfgfile.length() == 0) { - return; - } - - boost::property_tree::ptree pt = ConfigurableParamReaders::readConfigFile(cfgfile); - std::vector> keyValPairs; auto request = o2::utils::Str::tokenize(paramsList, ',', true); std::unordered_map requestMap; @@ -913,7 +921,7 @@ void ConfigurableParam::updateFromFile(std::string const& configFile, std::strin auto name = subKey.first; auto value = subKey.second.get_value(); std::string key = mainKey + "." + name; - if (!unchangedOnly || getProvenance(key) == kCODE) { + if (!unchangedOnly || ConfigurableParam::getProvenance(key) == ConfigurableParam::kCODE) { std::pair pair = std::make_pair(key, o2::utils::Str::trim_copy(value)); keyValPairs.push_back(pair); } @@ -928,16 +936,62 @@ void ConfigurableParam::updateFromFile(std::string const& configFile, std::strin // make sure all requested params were retrieved for (const auto& req : requestMap) { if (req.second == 0) { - throw std::runtime_error(fmt::format("Param {:s} was not found in {:s}", req.first, configFile)); + throw std::runtime_error(fmt::format("Param {:s} was not found in {:s}", req.first, source)); } } try { - setValues(keyValPairs); + ConfigurableParam::setValues(keyValPairs); } catch (std::exception const& error) { LOG(error) << "Error while setting values " << error.what(); } } +} // namespace + +// Update the storage map of params from the given configuration file. +// It can be in JSON or INI format. +// If nonempty comma-separated paramsList is provided, only those params will +// be updated, absence of data for any of requested params will lead to fatal +// If unchangedOnly is true, then only those parameters whose provenance is kCODE will be updated +// (to allow preference of run-time settings) +void ConfigurableParam::updateFromFile(std::string const& configFile, std::string const& paramsList, bool unchangedOnly) +{ + if (!sIsFullyInitialized) { + initialize(); + } + + auto cfgfile = o2::utils::Str::trim_copy(configFile); + + if (cfgfile.length() == 0) { + return; + } + + updateFromPropertyTree(ConfigurableParamReaders::readConfigFile(cfgfile), configFile, paramsList, unchangedOnly); +} + +// ------------------------------------------------------------------ + +void ConfigurableParam::updateFromJSONString(std::string const& configJSON, std::string const& paramsList, bool unchangedOnly) +{ + if (!sIsFullyInitialized) { + initialize(); + } + + auto json = o2::utils::Str::trim_copy(configJSON); + if (json.length() == 0) { + return; + } + + boost::property_tree::ptree pt; + std::istringstream input(json); + try { + boost::property_tree::read_json(input, pt); + } catch (const boost::property_tree::ptree_error& e) { + LOG(fatal) << "Failed to read JSON config string (" << e.what() << ")"; + } + + updateFromPropertyTree(pt, "provided JSON string", paramsList, unchangedOnly); +} // ------------------------------------------------------------------ // ------------------------------------------------------------------ diff --git a/Common/Utils/test/testConfigurableParam.cxx b/Common/Utils/test/testConfigurableParam.cxx index f0c3c59c79c35..6fd0344cdd1be 100644 --- a/Common/Utils/test/testConfigurableParam.cxx +++ b/Common/Utils/test/testConfigurableParam.cxx @@ -151,6 +151,42 @@ BOOST_AUTO_TEST_CASE(ConfigurableParam_FileIO_Json) std::remove(testFileName.c_str()); } +BOOST_AUTO_TEST_CASE(ConfigurableParam_JSONString_UnchangedOnly) +{ + ConfigurableParam::setValue("TestParam.ulValue", "2"); + ConfigurableParam::setProvenance("TestParam", "lValue", ConfigurableParam::kCODE); + ConfigurableParam::setProvenance("TestParam", "ulValue", ConfigurableParam::kRT); + ConfigurableParam::updateFromJSONString(R"json({"TestParam":{"lValue":"77","ulValue":"88"}})json", "TestParam", true); + + BOOST_CHECK_EQUAL(TestParam::Instance().lValue, 77); + BOOST_CHECK_EQUAL(TestParam::Instance().ulValue, 2); +} + +BOOST_AUTO_TEST_CASE(ConfigurableParam_JSONString_FromAsJSON) +{ + ConfigurableParam::setValue("TestParam.iValue", "321"); + ConfigurableParam::setValue("TestParam.sValue", "json-source"); + ConfigurableParam::setValues({{"TestParam.map", "{7:8,9:10}"}}); + const auto mapBefore = TestParam::Instance().map; + const auto json = ConfigurableParam::asJSON("TestParam"); + + ConfigurableParam::setValue("TestParam.iValue", "999"); + ConfigurableParam::setValue("TestParam.sValue", "json-modified"); + ConfigurableParam::setValues({{"TestParam.map", "{1:2}"}}); + ConfigurableParam::updateFromJSONString(json, "TestParam"); + + BOOST_CHECK_EQUAL(TestParam::Instance().iValue, 321); + BOOST_CHECK_EQUAL(TestParam::Instance().sValue, "json-source"); + BOOST_CHECK_EQUAL(TestParam::Instance().map.size(), mapBefore.size()); + BOOST_CHECK_EQUAL(TestParam::Instance().map.at(7), mapBefore.at(7)); + BOOST_CHECK_EQUAL(TestParam::Instance().map.at(9), mapBefore.at(9)); +} + +BOOST_AUTO_TEST_CASE(ConfigurableParam_JSONString_ParamsListMissing) +{ + BOOST_CHECK_THROW(ConfigurableParam::updateFromJSONString(ConfigurableParam::asJSON("TestParam"), "MissingParam"), std::runtime_error); +} + BOOST_AUTO_TEST_CASE(ConfigurableParam_FileIO_ROOT) { // test for root file serialization diff --git a/Detectors/GlobalTrackingWorkflow/src/CosmicsMatchingSpec.cxx b/Detectors/GlobalTrackingWorkflow/src/CosmicsMatchingSpec.cxx index d10330db09ea2..b76919fc241b6 100644 --- a/Detectors/GlobalTrackingWorkflow/src/CosmicsMatchingSpec.cxx +++ b/Detectors/GlobalTrackingWorkflow/src/CosmicsMatchingSpec.cxx @@ -11,10 +11,13 @@ /// @file CosmicsMatchingSpec.cxx +#include +#include #include #include #include "TStopwatch.h" #include "GlobalTracking/MatchCosmics.h" +#include "GlobalTracking/MatchCosmicsParams.h" #include "DataFormatsITSMFT/TopologyDictionary.h" #include "DataFormatsTPC/Constants.h" #include "ReconstructionDataFormats/GlobalTrackID.h" @@ -104,6 +107,18 @@ void CosmicsMatchingSpec::run(ProcessingContext& pc) if (mUseMC) { pc.outputs().snapshot(Output{"GLO", "COSMICTRC_MC", 0}, mMatching.getCosmicTracksLbl()); } + static bool first = true; + if (first) { + first = false; + const auto& conf = MatchCosmicsParams::Instance(); + if (pc.services().get().inputTimesliceId == 0) { + o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, conf.getName()), conf.getName()); + TMap md; + md.SetOwnerKeyValue(); + md.Add(new TObjString(conf.getName().c_str()), new TObjString(o2::conf::ConfigurableParam::asJSON(conf.getName()).c_str())); + pc.outputs().snapshot(Output{"META", "COSMICMATCHER", 0}, md); + } + } mTimer.Stop(); } @@ -193,6 +208,8 @@ DataProcessorSpec getCosmicsMatchingSpec(GTrackID::mask_t src, bool usePV, bool o2::tpc::VDriftHelper::requestCCDBInputs(dataRequest->inputs); dataRequest->inputs.emplace_back("corrMap", o2::header::gDataOriginTPC, "TPCCORRMAP", 0, Lifetime::Timeframe); + outputs.emplace_back("META", "COSMICMATCHER", 0, Lifetime::Sporadic); + return DataProcessorSpec{ "cosmics-matcher", dataRequest->inputs, diff --git a/Detectors/GlobalTrackingWorkflow/src/PrimaryVertexingSpec.cxx b/Detectors/GlobalTrackingWorkflow/src/PrimaryVertexingSpec.cxx index c1d7b62bbf731..72e6cb58fffa2 100644 --- a/Detectors/GlobalTrackingWorkflow/src/PrimaryVertexingSpec.cxx +++ b/Detectors/GlobalTrackingWorkflow/src/PrimaryVertexingSpec.cxx @@ -11,12 +11,15 @@ /// @file PrimaryVertexingSpec.cxx +#include +#include #include #include #include "DataFormatsGlobalTracking/RecoContainer.h" #include "DataFormatsGlobalTracking/RecoContainerCreateTracksVariadic.h" #include "DataFormatsITSMFT/TrkClusRef.h" #include "DataFormatsCalibration/MeanVertexObject.h" +#include "DataFormatsCalibration/MeanVertexBiasParam.h" #include "ReconstructionDataFormats/TrackTPCITS.h" #include "ReconstructionDataFormats/GlobalTrackID.h" #include "DetectorsBase/Propagator.h" @@ -201,8 +204,16 @@ void PrimaryVertexingSpec::run(ProcessingContext& pc) static bool first = true; if (first) { first = false; + const auto& confPV = PVertexerParams::Instance(); + const auto& confMV = o2::dataformats::MeanVertexBiasParam::Instance(); if (pc.services().get().inputTimesliceId == 0) { - o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, PVertexerParams::Instance().getName()), PVertexerParams::Instance().getName()); + o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, confPV.getName()), confPV.getName()); + o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, confMV.getName()), confMV.getName()); + TMap md; + md.SetOwnerKeyValue(); + md.Add(new TObjString(confPV.getName().c_str()), new TObjString(o2::conf::ConfigurableParam::asJSON(confPV.getName()).c_str())); + md.Add(new TObjString(confMV.getName().c_str()), new TObjString(o2::conf::ConfigurableParam::asJSON(confMV.getName()).c_str())); + pc.outputs().snapshot(Output{"META", "PVERTEXER", 0}, md); } } } @@ -289,6 +300,8 @@ DataProcessorSpec getPrimaryVertexingSpec(GTrackID::mask_t src, bool skip, bool true); dataRequest->inputs.emplace_back("meanvtx", "GLO", "MEANVERTEX", 0, Lifetime::Condition, ccdbParamSpec("GLO/Calib/MeanVertex", {}, 1)); + outputs.emplace_back("META", "PVERTEXER", 0, Lifetime::Sporadic); + return DataProcessorSpec{ "primary-vertexing", dataRequest->inputs, diff --git a/Detectors/GlobalTrackingWorkflow/src/SecondaryVertexingSpec.cxx b/Detectors/GlobalTrackingWorkflow/src/SecondaryVertexingSpec.cxx index afce2861be2fb..68537f61d3797 100644 --- a/Detectors/GlobalTrackingWorkflow/src/SecondaryVertexingSpec.cxx +++ b/Detectors/GlobalTrackingWorkflow/src/SecondaryVertexingSpec.cxx @@ -11,6 +11,8 @@ /// @file SecondaryVertexingSpec.cxx +#include +#include #include #include "DataFormatsCalibration/MeanVertexObject.h" #include "Framework/CCDBParamSpec.h" @@ -124,10 +126,17 @@ void SecondaryVertexingSpec::run(ProcessingContext& pc) if (first) { first = false; if (pc.services().get().inputTimesliceId == 0) { - o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, SVertexerParams::Instance().getName()), SVertexerParams::Instance().getName()); + const auto& confSV = SVertexerParams::Instance(); + o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, confSV.getName()), confSV.getName()); + TMap md; + md.SetOwnerKeyValue(); + md.Add(new TObjString(confSV.getName().c_str()), new TObjString(o2::conf::ConfigurableParam::asJSON(confSV.getName()).c_str())); if (mEnableStrangenessTracking) { - o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, o2::strangeness_tracking::StrangenessTrackingParamConfig::Instance().getName()), o2::strangeness_tracking::StrangenessTrackingParamConfig::Instance().getName()); + const auto& confST = o2::strangeness_tracking::StrangenessTrackingParamConfig::Instance(); + o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, confST().getName()), confST.getName()); + md.Add(new TObjString(confST.getName().c_str()), new TObjString(o2::conf::ConfigurableParam::asJSON(confST.getName()).c_str())); } + pc.outputs().snapshot(Output{"META", "SVERTEXER", 0}, md); } } } @@ -298,6 +307,7 @@ DataProcessorSpec getSecondaryVertexingSpec(GTrackID::mask_t src, bool enableCas LOG(info) << "Strangeness tracker will use MC"; } } + outputs.emplace_back("META", "SVERTEXER", 0, Lifetime::Sporadic); return DataProcessorSpec{ "secondary-vertexing", diff --git a/Detectors/GlobalTrackingWorkflow/src/StrangenessTrackingSpec.cxx b/Detectors/GlobalTrackingWorkflow/src/StrangenessTrackingSpec.cxx index e313940b0a91e..c1882c453d2d2 100644 --- a/Detectors/GlobalTrackingWorkflow/src/StrangenessTrackingSpec.cxx +++ b/Detectors/GlobalTrackingWorkflow/src/StrangenessTrackingSpec.cxx @@ -11,6 +11,8 @@ /// \file StrangenessTrackingSpec.cxx /// \brief +#include +#include #include "TGeoGlobalMagField.h" #include "Framework/ConfigParamRegistry.h" #include "Field/MagneticField.h" @@ -80,6 +82,18 @@ void StrangenessTrackerSpec::run(framework::ProcessingContext& pc) pc.outputs().snapshot(Output{"GLO", "STRANGETRACKS_MC", 0}, mTracker.getStrangeTrackLabels()); } + static bool first = true; + if (first) { + first = false; + if (pc.services().get().inputTimesliceId == 0) { + const auto& conf = StrangenessTrackingParamConfig::Instance(); + o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, conf.getName()), conf.getName()); + TMap md; + md.SetOwnerKeyValue(); + md.Add(new TObjString(conf.getName().c_str()), new TObjString(o2::conf::ConfigurableParam::asJSON(conf.getName()).c_str())); + pc.outputs().snapshot(Output{"META", "STRTRACKER", 0}, md); + } + } mTimer.Stop(); } @@ -162,6 +176,7 @@ DataProcessorSpec getStrangenessTrackerSpec(o2::dataformats::GlobalTrackID::mask outputs.emplace_back("GLO", "STRANGETRACKS_MC", 0, Lifetime::Timeframe); LOG(info) << "Strangeness tracker will use MC"; } + outputs.emplace_back("META", "STRTRACKER", 0, Lifetime::Sporadic); return DataProcessorSpec{ "strangeness-tracker", diff --git a/Detectors/GlobalTrackingWorkflow/src/TOFMatcherSpec.cxx b/Detectors/GlobalTrackingWorkflow/src/TOFMatcherSpec.cxx index 746e572c506b8..a841a32441f68 100644 --- a/Detectors/GlobalTrackingWorkflow/src/TOFMatcherSpec.cxx +++ b/Detectors/GlobalTrackingWorkflow/src/TOFMatcherSpec.cxx @@ -13,6 +13,8 @@ #include #include +#include +#include #include "TStopwatch.h" #include "Framework/ConfigParamRegistry.h" #include "DetectorsBase/GeometryManager.h" @@ -218,7 +220,12 @@ void TOFMatcherSpec::run(ProcessingContext& pc) if (first) { first = false; if (pc.services().get().inputTimesliceId == 0) { - o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, MatchTOFParams::Instance().getName()), MatchTOFParams::Instance().getName()); + const auto& conf = MatchTOFParams::Instance(); + o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, conf.getName()), conf.getName()); + TMap md; + md.SetOwnerKeyValue(); + md.Add(new TObjString(conf.getName().c_str()), new TObjString(o2::conf::ConfigurableParam::asJSON(conf.getName()).c_str())); + pc.outputs().snapshot(Output{"META", "TOFMATCHER", 0}, md); } } @@ -309,6 +316,7 @@ DataProcessorSpec getTOFMatcherSpec(GID::mask_t src, bool useMC, bool useFIT, bo outputs.emplace_back(o2::header::gDataOriginTOF, "MATCHABLES_16", 0, Lifetime::Timeframe); outputs.emplace_back(o2::header::gDataOriginTOF, "MATCHABLES_17", 0, Lifetime::Timeframe); } + outputs.emplace_back("META", "TOFMATCHER", 0, Lifetime::Sporadic); return DataProcessorSpec{ "tof-matcher", diff --git a/Detectors/GlobalTrackingWorkflow/src/TPCITSMatchingSpec.cxx b/Detectors/GlobalTrackingWorkflow/src/TPCITSMatchingSpec.cxx index 079fe5455fd4a..8ed5df090010f 100644 --- a/Detectors/GlobalTrackingWorkflow/src/TPCITSMatchingSpec.cxx +++ b/Detectors/GlobalTrackingWorkflow/src/TPCITSMatchingSpec.cxx @@ -12,7 +12,8 @@ /// @file TPCITSMatchingSpec.cxx #include - +#include +#include #include "GlobalTracking/MatchTPCITS.h" #include "GlobalTracking/MatchTPCITSParams.h" #include "DataFormatsITSMFT/TopologyDictionary.h" @@ -128,8 +129,13 @@ void TPCITSMatchingDPL::run(ProcessingContext& pc) static bool first = true; if (first) { first = false; + const auto& conf = MatchTPCITSParams::Instance(); if (pc.services().get().inputTimesliceId == 0) { - o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, MatchTPCITSParams::Instance().getName()), MatchTPCITSParams::Instance().getName()); + o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, conf.getName()), conf.getName()); + TMap md; + md.SetOwnerKeyValue(); + md.Add(new TObjString(conf.getName().c_str()), new TObjString(o2::conf::ConfigurableParam::asJSON(conf.getName()).c_str())); + pc.outputs().snapshot(Output{"META", "TPCITSMATCHER", 0}, md); } } @@ -295,6 +301,9 @@ DataProcessorSpec getTPCITSMatchingSpec(GTrackID::mask_t src, bool useFT0, bool if (requestCTPLumi) { dataRequest->inputs.emplace_back("lumiCTP", o2::header::gDataOriginCTP, "LUMICTP", 0, Lifetime::Timeframe); } + + outputs.emplace_back("META", "TPCITSMATCHER", 0, Lifetime::Sporadic); + return DataProcessorSpec{ "itstpc-track-matcher", dataRequest->inputs, diff --git a/Detectors/ITSMFT/ITS/workflow/src/TrackerSpec.cxx b/Detectors/ITSMFT/ITS/workflow/src/TrackerSpec.cxx index bbafc48e931ed..bfa1b05f08923 100644 --- a/Detectors/ITSMFT/ITS/workflow/src/TrackerSpec.cxx +++ b/Detectors/ITSMFT/ITS/workflow/src/TrackerSpec.cxx @@ -10,7 +10,8 @@ // or submit itself to any jurisdiction. #include - +#include +#include #include "Framework/ControlService.h" #include "Framework/ConfigParamRegistry.h" #include "Framework/CCDBParamSpec.h" @@ -67,8 +68,15 @@ void TrackerDPL::run(ProcessingContext& pc) if (first) { first = false; if (pc.services().get().inputTimesliceId == 0) { - o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, o2::its::VertexerParamConfig::Instance().getName()), o2::its::VertexerParamConfig::Instance().getName()); - o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, o2::its::TrackerParamConfig::Instance().getName()), o2::its::TrackerParamConfig::Instance().getName()); + const auto& vtconf = o2::its::VertexerParamConfig::Instance(); + const auto& trconf = o2::its::TrackerParamConfig::Instance(); + o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, vtconf.getName()), vtconf.getName()); + o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, trconf.getName()), trconf.getName()); + TMap md; + md.SetOwnerKeyValue(); + md.Add(new TObjString(vtconf.getName().c_str()), new TObjString(o2::conf::ConfigurableParam::asJSON(vtconf.getName()).c_str())); + md.Add(new TObjString(trconf.getName().c_str()), new TObjString(o2::conf::ConfigurableParam::asJSON(trconf.getName()).c_str())); + pc.outputs().snapshot(Output{"META", "ITSTRACKER", 0}, md); } } } @@ -138,6 +146,7 @@ DataProcessorSpec getTrackerSpec(bool useMC, bool doStag, bool useGeom, int trgT outputs.emplace_back("ITS", "VERTICESMCPUR", 0, Lifetime::Timeframe); outputs.emplace_back("ITS", "TRACKSMCTR", 0, Lifetime::Timeframe); } + outputs.emplace_back("META", "ITSTRACKER", 0, Lifetime::Sporadic); return DataProcessorSpec{ .name = "its-tracker", diff --git a/Detectors/ITSMFT/MFT/workflow/src/TrackerSpec.cxx b/Detectors/ITSMFT/MFT/workflow/src/TrackerSpec.cxx index 6ceb04b3c4df6..0e8e883775666 100644 --- a/Detectors/ITSMFT/MFT/workflow/src/TrackerSpec.cxx +++ b/Detectors/ITSMFT/MFT/workflow/src/TrackerSpec.cxx @@ -18,7 +18,8 @@ #include "MFTTracking/Tracker.h" #include "MFTTracking/TrackCA.h" #include "MFTBase/GeometryTGeo.h" - +#include +#include #include #include @@ -329,7 +330,12 @@ void TrackerDPL::run(ProcessingContext& pc) if (first) { first = false; if (pc.services().get().inputTimesliceId == 0) { - o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, o2::mft::MFTTrackingParam::Instance().getName()), o2::mft::MFTTrackingParam::Instance().getName()); + const auto& conf = o2::mft::MFTTrackingParam::Instance(); + o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, conf.getName()), conf.getName()); + TMap md; + md.SetOwnerKeyValue(); + md.Add(new TObjString(conf.getName().c_str()), new TObjString(o2::conf::ConfigurableParam::asJSON(conf.getName()).c_str())); + pc.outputs().snapshot(Output{"META", "MFTTRACKER", 0}, md); } } @@ -462,6 +468,8 @@ DataProcessorSpec getTrackerSpec(bool useMC, bool useGeom, int nThreads) outputs.emplace_back("MFT", "TRACKSMCTR", 0, Lifetime::Timeframe); } + outputs.emplace_back("META", "MFTTRACKER", 0, Lifetime::Sporadic); + return DataProcessorSpec{ "mft-tracker", inputs, diff --git a/Detectors/TRD/workflow/src/TRDGlobalTrackingSpec.cxx b/Detectors/TRD/workflow/src/TRDGlobalTrackingSpec.cxx index 4aecd0e3df054..8d27362316339 100644 --- a/Detectors/TRD/workflow/src/TRDGlobalTrackingSpec.cxx +++ b/Detectors/TRD/workflow/src/TRDGlobalTrackingSpec.cxx @@ -11,6 +11,8 @@ /// @file TRDGlobalTrackingSpec.cxx +#include +#include #include "TRDWorkflow/TRDGlobalTrackingSpec.h" #include "TRDBase/Geometry.h" #include "DetectorsCommonDataFormats/DetectorNameConf.h" @@ -569,6 +571,10 @@ void TRDGlobalTracking::run(ProcessingContext& pc) first = false; if (pc.services().get().inputTimesliceId == 0) { o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, "GPU_rec_trd"), "GPU_rec_trd"); + TMap md; + md.SetOwnerKeyValue(); + md.Add(new TObjString(o2::gpu::internal::GPUConfigurableParamGPUSettingsRecTRD::Instance().getName().c_str()), new TObjString(o2::conf::ConfigurableParam::asJSON(o2::gpu::internal::GPUConfigurableParamGPUSettingsRecTRD::Instance().getName()).c_str())); + pc.outputs().snapshot(Output{"META", "TRDTRACKER", 0}, md); } } @@ -1019,6 +1025,8 @@ DataProcessorSpec getTRDGlobalTrackingSpec(bool useMC, GTrackID::mask_t src, boo } } + outputs.emplace_back("META", "TRDTRACKER", 0, Lifetime::Sporadic); + std::string processorName = o2::utils::Str::concat_string("trd-globaltracking", GTrackID::getSourcesNames(src)); std::regex reg("[,\\[\\]]+"); processorName = regex_replace(processorName, reg, "_"); diff --git a/Framework/AnalysisSupport/src/AODWriterHelpers.cxx b/Framework/AnalysisSupport/src/AODWriterHelpers.cxx index 5b5829d96a1de..86a8aa1694a99 100644 --- a/Framework/AnalysisSupport/src/AODWriterHelpers.cxx +++ b/Framework/AnalysisSupport/src/AODWriterHelpers.cxx @@ -16,6 +16,7 @@ #include "Framework/EndOfStreamContext.h" #include "Framework/ProcessingContext.h" #include "Framework/InitContext.h" +#include "Framework/Output.h" #include "Framework/CallbackService.h" #include "Framework/AnalysisSupportHelpers.h" #include "Framework/TableConsumer.h" @@ -31,6 +32,10 @@ #include #include #include +#include +#include +#include +#include O2_DECLARE_DYNAMIC_LOG(histogram_registry); @@ -477,4 +482,46 @@ AlgorithmSpec AODWriterHelpers::getOutputObjHistWriter(ConfigContext const& /*ct }; }}; } + +AlgorithmSpec AODWriterHelpers::getMetadataCollector(ConfigContext const& /*ctx*/) +{ + return AlgorithmSpec{[](InitContext&) -> std::function { + // Accumulated metadata, last writer wins per key. + auto merged = std::make_shared>>(); + return [merged](ProcessingContext& pc) -> void { + auto nParts = pc.inputs().getNofParts(0); + for (auto pi = 0U; pi < nParts; ++pi) { + auto part = pc.inputs().get("meta", pi); + if (!part) { + continue; + } + TIter next(part.get()); + while (TObject* key = next()) { + TObject* value = part->GetValue(key); + std::string k = key->GetName(); + std::string v = value != nullptr ? value->GetName() : ""; + auto it = std::ranges::find_if(*merged, + [&k](auto const& e) { return e.first == k; }); + if (it != merged->end()) { + it->second = std::move(v); + } else { + merged->emplace_back(std::move(k), std::move(v)); + } + } + } + // Emit the keys/vals vectors the AOD writer already turns into the AO2D + // metaData TMap, so no special handling is needed there. + std::vector keys, vals; + keys.reserve(merged->size()); + vals.reserve(merged->size()); + for (auto const& [k, v] : *merged) { + keys.emplace_back(k); + vals.emplace_back(v); + } + LOG(debug) << "metadata-collector: emitting " << keys.size() << " aggregated metadata entries"; + pc.outputs().snapshot(Output{"AMD", "AODMetadataKeys", 0}, keys); + pc.outputs().snapshot(Output{"AMD", "AODMetadataVals", 0}, vals); + }; + }}; +} } // namespace o2::framework::writers diff --git a/Framework/AnalysisSupport/src/AODWriterHelpers.h b/Framework/AnalysisSupport/src/AODWriterHelpers.h index 7ae59a5cf3b01..65b068cb630a7 100644 --- a/Framework/AnalysisSupport/src/AODWriterHelpers.h +++ b/Framework/AnalysisSupport/src/AODWriterHelpers.h @@ -21,6 +21,7 @@ namespace o2::framework::writers struct AODWriterHelpers { static AlgorithmSpec getOutputObjHistWriter(ConfigContext const& context); static AlgorithmSpec getOutputTTreeWriter(ConfigContext const& context); + static AlgorithmSpec getMetadataCollector(ConfigContext const& context); }; } // namespace o2::framework::writers diff --git a/Framework/AnalysisSupport/src/Plugin.cxx b/Framework/AnalysisSupport/src/Plugin.cxx index 5f61a236cbd58..f20475a1a2bd2 100644 --- a/Framework/AnalysisSupport/src/Plugin.cxx +++ b/Framework/AnalysisSupport/src/Plugin.cxx @@ -54,6 +54,13 @@ struct ROOTTTreeWriter : o2::framework::AlgorithmPlugin { } }; +struct ROOTMetadataCollector : o2::framework::AlgorithmPlugin { + o2::framework::AlgorithmSpec create(o2::framework::ConfigContext const& config) override + { + return o2::framework::writers::AODWriterHelpers::getMetadataCollector(config); + } +}; + using namespace o2::framework; struct RunSummary : o2::framework::ServicePlugin { o2::framework::ServiceSpec* create() final @@ -286,6 +293,7 @@ DEFINE_DPL_PLUGINS_BEGIN DEFINE_DPL_PLUGIN_INSTANCE(ROOTFileReader, CustomAlgorithm); DEFINE_DPL_PLUGIN_INSTANCE(ROOTObjWriter, CustomAlgorithm); DEFINE_DPL_PLUGIN_INSTANCE(ROOTTTreeWriter, CustomAlgorithm); +DEFINE_DPL_PLUGIN_INSTANCE(ROOTMetadataCollector, CustomAlgorithm); DEFINE_DPL_PLUGIN_INSTANCE(RunSummary, CustomService); DEFINE_DPL_PLUGIN_INSTANCE(DiscoverMetadataInAOD, ConfigDiscovery); DEFINE_DPL_PLUGINS_END diff --git a/Framework/Core/include/Framework/AnalysisSupportHelpers.h b/Framework/Core/include/Framework/AnalysisSupportHelpers.h index c1968123e765d..8eaaa4f1a61a6 100644 --- a/Framework/Core/include/Framework/AnalysisSupportHelpers.h +++ b/Framework/Core/include/Framework/AnalysisSupportHelpers.h @@ -48,6 +48,9 @@ struct AnalysisSupportHelpers { static DataProcessorSpec getOutputObjHistSink(ConfigContext const&); /// writes inputs of kind AOD to file static DataProcessorSpec getGlobalAODSink(ConfigContext const&); + /// Match all inputs of kind META, merge them and republish as the AOD + /// metadata keys/vals consumed by the AOD writer. + static DataProcessorSpec getMetadataCollectorSink(ConfigContext const&); /// Get the data director static std::shared_ptr getDataOutputDirector(ConfigContext const& ctx); }; diff --git a/Framework/Core/src/AnalysisSupportHelpers.cxx b/Framework/Core/src/AnalysisSupportHelpers.cxx index c16a1da61ae8a..ba6cb23e8652f 100644 --- a/Framework/Core/src/AnalysisSupportHelpers.cxx +++ b/Framework/Core/src/AnalysisSupportHelpers.cxx @@ -220,4 +220,23 @@ DataProcessorSpec return spec; } + +DataProcessorSpec + AnalysisSupportHelpers::getMetadataCollectorSink(ConfigContext const& ctx) +{ + // Lifetime is sporadic because META messages are not produced every + // timeframe. The oldest-possible-timeframe completion policy (registered + // in CompletionPolicy::createDefaultPolicies) decides when the collected + // parts are merged and republished as the AOD metadata keys/vals that the + // AOD writer turns into the AO2D metaData object. + DataProcessorSpec spec{ + .name = "internal-dpl-metadata-collector", + .inputs = {InputSpec("meta", DataSpecUtils::dataDescriptorMatcherFrom(header::DataOrigin{"META"}), Lifetime::Sporadic)}, + .outputs = {OutputSpec{OutputLabel{"keys"}, header::DataOrigin{"AMD"}, header::DataDescription{"AODMetadataKeys"}, 0, Lifetime::Sporadic}, + OutputSpec{OutputLabel{"vals"}, header::DataOrigin{"AMD"}, header::DataDescription{"AODMetadataVals"}, 0, Lifetime::Sporadic}}, + .algorithm = PluginManager::loadAlgorithmFromPlugin("O2FrameworkAnalysisSupport", "ROOTMetadataCollector", ctx), + }; + + return spec; +} } // namespace o2::framework diff --git a/Framework/Core/src/CompletionPolicy.cxx b/Framework/Core/src/CompletionPolicy.cxx index a09028b9249f3..27b5485207978 100644 --- a/Framework/Core/src/CompletionPolicy.cxx +++ b/Framework/Core/src/CompletionPolicy.cxx @@ -27,6 +27,7 @@ std::vector return { CompletionPolicyHelpers::consumeWhenAllOrdered("internal-dpl-aod-writer"), CompletionPolicyHelpers::consumeWhenAnyZeroCount("internal-dpl-injected-dummy-sink", [](DeviceSpec const& s) { return s.name.find("internal-dpl-injected-dummy-sink") != std::string::npos; }), + CompletionPolicyHelpers::consumeWhenPastOldestPossibleTimeframe("internal-dpl-metadata-collector", [](DeviceSpec const& s) { return s.name == "internal-dpl-metadata-collector"; }), CompletionPolicyHelpers::consumeWhenAll()}; } diff --git a/Framework/Core/src/WorkflowHelpers.cxx b/Framework/Core/src/WorkflowHelpers.cxx index 188b6653c6a43..7dfee0a4d317e 100644 --- a/Framework/Core/src/WorkflowHelpers.cxx +++ b/Framework/Core/src/WorkflowHelpers.cxx @@ -236,6 +236,7 @@ void WorkflowHelpers::injectServiceDevices(WorkflowSpec& workflow, ConfigContext std::vector requestedCCDBs; std::vector providedCCDBs; + bool hasMetaOutput = false; for (size_t wi = 0; wi < workflow.size(); ++wi) { auto& processor = workflow[wi]; @@ -392,6 +393,8 @@ void WorkflowHelpers::injectServiceDevices(WorkflowSpec& workflow, ConfigContext } else { it->bindings.push_back(output.binding.value); } + } else if (DataSpecUtils::partialMatch(output, header::DataOrigin{"META"})) { + hasMetaOutput = true; } if (output.lifetime == Lifetime::Condition) { @@ -583,6 +586,12 @@ void WorkflowHelpers::injectServiceDevices(WorkflowSpec& workflow, ConfigContext extraSpecs.push_back(rootSink); } + // Inject a collector which merges all META messages and republishes them as + // the AOD metadata keys/vals the AOD writer writes into the AO2D file. + if (hasMetaOutput) { + extraSpecs.push_back(AnalysisSupportHelpers::getMetadataCollectorSink(ctx)); + } + workflow.insert(workflow.end(), extraSpecs.begin(), extraSpecs.end()); extraSpecs.clear(); @@ -744,13 +753,41 @@ void WorkflowHelpers::injectAODWriter(WorkflowSpec& workflow, ConfigContext cons // create DataOutputDescriptor std::shared_ptr dod = AnalysisSupportHelpers::getDataOutputDirector(ctx); + auto hasWritableAOD = std::ranges::any_of(dec.outputsInputs, [](InputSpec const& spec) { + return DataSpecUtils::partialMatch(spec, writableAODOrigins); + }); + auto hasTFNumber = std::ranges::any_of(dec.outputsInputs, [](InputSpec const& spec) { + return DataSpecUtils::partialMatch(spec, header::DataOrigin{"TFN"}) && + DataSpecUtils::partialMatch(spec, header::DataDescription{"TFNumber"}); + }); + auto hasTFFileName = std::ranges::any_of(dec.outputsInputs, [](InputSpec const& spec) { + return DataSpecUtils::partialMatch(spec, header::DataOrigin{"TFF"}) && + DataSpecUtils::partialMatch(spec, header::DataDescription{"TFFilename"}); + }); + // AMD metadata is part of AODOrigins, but metadata alone is not enough to + // run the AOD writer unless the same topology can provide TF bookkeeping. + auto canWriteMetadataOnly = hasTFNumber && hasTFFileName; + // select outputs of type AOD which need to be saved dec.outputsInputsAOD.clear(); for (auto ii = 0u; ii < dec.outputsInputs.size(); ii++) { - if (DataSpecUtils::partialMatch(dec.outputsInputs[ii], AODOrigins)) { - auto ds = dod->getDataOutputDescriptors(dec.outputsInputs[ii]); - if (ds.size() > 0 || dec.isDangling[ii]) { - dec.outputsInputsAOD.emplace_back(dec.outputsInputs[ii]); + auto const& input = dec.outputsInputs[ii]; + if (!DataSpecUtils::partialMatch(input, AODOrigins)) { + continue; + } + // Avoid injecting an AOD writer in detector-only subworkflows where the + // only AOD-like output is AMD metadata from the META collector. + if (DataSpecUtils::partialMatch(input, header::DataOrigin{"AMD"}) && !hasWritableAOD && !canWriteMetadataOnly) { + continue; + } + auto ds = dod->getDataOutputDescriptors(input); + if (ds.size() > 0 || dec.isDangling[ii]) { + dec.outputsInputsAOD.emplace_back(input); + if (DataSpecUtils::partialMatch(input, header::DataOrigin{"AMD"})) { + // Metadata collected from META outputs is republished as AMD sporadic data. + // Keep the AOD writer input sporadic so it can consume it together with + // the timeframe AMD metadata emitted directly by AOD producers. + dec.outputsInputsAOD.back().lifetime = Lifetime::Sporadic; } } } @@ -763,15 +800,18 @@ void WorkflowHelpers::injectAODWriter(WorkflowSpec& workflow, ConfigContext cons auto fileSink = AnalysisSupportHelpers::getGlobalAODSink(ctx); workflow.push_back(fileSink); - auto it = std::find_if(dec.outputsInputs.begin(), dec.outputsInputs.end(), [](InputSpec const& spec) -> bool { - return DataSpecUtils::partialMatch(spec, o2::header::DataOrigin("TFN")); - }); - dec.isDangling[std::distance(dec.outputsInputs.begin(), it)] = false; - - it = std::find_if(dec.outputsInputs.begin(), dec.outputsInputs.end(), [](InputSpec const& spec) -> bool { - return DataSpecUtils::partialMatch(spec, o2::header::DataOrigin("TFF")); - }); - dec.isDangling[std::distance(dec.outputsInputs.begin(), it)] = false; + // TFN/TFF are added to the writer inputs above, but they may be absent in + // metadata-only topologies. Only mark them as non-dangling when present. + auto markAsMatched = [&](header::DataOrigin origin) { + auto it = std::find_if(dec.outputsInputs.begin(), dec.outputsInputs.end(), [&](InputSpec const& spec) -> bool { + return DataSpecUtils::partialMatch(spec, origin); + }); + if (it != dec.outputsInputs.end()) { + dec.isDangling[std::distance(dec.outputsInputs.begin(), it)] = false; + } + }; + markAsMatched(header::DataOrigin{"TFN"}); + markAsMatched(header::DataOrigin{"TFF"}); } } diff --git a/Framework/Core/test/test_WorkflowHelpers.cxx b/Framework/Core/test/test_WorkflowHelpers.cxx index d43e74558c0bb..6dd716a7dbbdb 100644 --- a/Framework/Core/test/test_WorkflowHelpers.cxx +++ b/Framework/Core/test/test_WorkflowHelpers.cxx @@ -20,6 +20,7 @@ #include #include #include +#include using namespace o2::framework; @@ -494,6 +495,37 @@ TEST_CASE("TestExternalInput") availableForwardsInfo); } +TEST_CASE("TestMetadataOnlyAODWriterInjection") +{ + auto hasProcessor = [](WorkflowSpec const& workflow, std::string_view name) { + return std::ranges::any_of(workflow, [name](DataProcessorSpec const& spec) { + return spec.name == name; + }); + }; + + WorkflowSpec metadataOnlyWorkflow{ + {.name = "metadata-producer", + .outputs = {OutputSpec{"META", "TRACKER", 0, Lifetime::Sporadic}}}}; + + auto context = makeEmptyConfigContext(); + WorkflowHelpers::injectServiceDevices(metadataOnlyWorkflow, *context); + + REQUIRE(hasProcessor(metadataOnlyWorkflow, "internal-dpl-metadata-collector")); + REQUIRE_FALSE(hasProcessor(metadataOnlyWorkflow, "internal-dpl-aod-writer")); + + WorkflowSpec metadataWithTFSourceWorkflow{ + {.name = "metadata-producer", + .outputs = {OutputSpec{"META", "TRACKER", 0, Lifetime::Sporadic}}}, + {.name = "tf-source", + .outputs = {OutputSpec{"TFN", "TFNumber"}, OutputSpec{"TFF", "TFFilename"}}}}; + + context = makeEmptyConfigContext(); + WorkflowHelpers::injectServiceDevices(metadataWithTFSourceWorkflow, *context); + + REQUIRE(hasProcessor(metadataWithTFSourceWorkflow, "internal-dpl-metadata-collector")); + REQUIRE(hasProcessor(metadataWithTFSourceWorkflow, "internal-dpl-aod-writer")); +} + TEST_CASE("DetermineDanglingOutputs") { WorkflowSpec workflow0{ diff --git a/Framework/TestWorkflows/src/o2TestHistograms.cxx b/Framework/TestWorkflows/src/o2TestHistograms.cxx index 9c2cba35b9156..e4e0549f866c3 100644 --- a/Framework/TestWorkflows/src/o2TestHistograms.cxx +++ b/Framework/TestWorkflows/src/o2TestHistograms.cxx @@ -16,11 +16,18 @@ #include "Framework/runDataProcessing.h" #include "Framework/AnalysisTask.h" #include +#include +#include +#include using namespace o2; using namespace o2::framework; using namespace o2::framework::expressions; +// pT cut applied when producing the skimmed derived dataset; the same value is +// published as run metadata so it lands in the derived AO2D's metaData object. +static constexpr float kSkimPtCut = 1.5f; + namespace o2::aod { O2ORIGIN("EMB"); @@ -38,8 +45,8 @@ DECLARE_SOA_TABLE(SkimmedExampleTrack, "AOD", "SKIMEXTRK", //! struct EtaAndClsHistogramsSimple { OutputObj etaClsH{TH2F("eta_vs_pt", "#eta vs pT", 102, -2.01, 2.01, 100, 0, 10)}; Produces skimEx; - Configurable trackFilterString{"track-filter", "o2::aod::track::pt < 10.f", "Track filter string"}; - Filter trackFilter = o2::aod::track::pt < 10.f; + Configurable trackFilterString{"track-filter", "", "Track filter string (overrides the pT cut when set)"}; + Filter trackFilter = o2::aod::track::pt < kSkimPtCut; HistogramRegistry registry{ "registry", @@ -88,8 +95,8 @@ struct EtaAndClsHistogramsSimple { struct EtaAndClsHistogramsIUSimple { OutputObj etaClsH{TH2F("eta_vs_pt", "#eta vs pT", 102, -2.01, 2.01, 100, 0, 10)}; Produces skimEx; - Configurable trackFilterString{"track-filter", "o2::aod::track::pt < 10.f", "Track filter string"}; - Filter trackFilter = o2::aod::track::pt < 10.f; + Configurable trackFilterString{"track-filter", "", "Track filter string (overrides the pT cut when set)"}; + Filter trackFilter = o2::aod::track::pt < kSkimPtCut; HistogramRegistry registry{ "registry", @@ -136,8 +143,8 @@ struct EtaAndClsHistogramsFull { } // }; - Configurable trackFilterString{"track-filter", "o2::aod::track::pt < 10.f", "Track filter string"}; - Filter trackFilter = o2::aod::track::pt < 10.f; + Configurable trackFilterString{"track-filter", "", "Track filter string (overrides the pT cut when set)"}; + Filter trackFilter = o2::aod::track::pt < kSkimPtCut; void init(InitContext&) { @@ -183,25 +190,37 @@ WorkflowSpec defineDataProcessing(ConfigContext const& cfgc) LOGP(info, "- {} present.", table); } // Notice it's important for the tasks to use the same name, otherwise topology generation will be confused. + WorkflowSpec specs; if (runType == "2" || !hasTrackCov) { LOGP(info, "Using only tracks {}", runType); if (hasTrackIU) { - return WorkflowSpec{ - adaptAnalysisTask(cfgc, TaskName{"simple-histos"}), - }; + specs = WorkflowSpec{adaptAnalysisTask(cfgc, TaskName{"simple-histos"})}; + } else { + specs = WorkflowSpec{adaptAnalysisTask(cfgc, TaskName{"simple-histos"})}; } - return WorkflowSpec{ - adaptAnalysisTask(cfgc, TaskName{"simple-histos"}), - }; } else { LOGP(info, "Using tracks extra {}", runType); if (hasTrackIU) { - return WorkflowSpec{ - adaptAnalysisTask(cfgc, TaskName{"simple-histos"}), - }; + specs = WorkflowSpec{adaptAnalysisTask(cfgc, TaskName{"simple-histos"})}; + } else { + specs = WorkflowSpec{adaptAnalysisTask(cfgc, TaskName{"simple-histos"})}; } - return WorkflowSpec{ - adaptAnalysisTask(cfgc, TaskName{"simple-histos"}), - }; } + + // Publish the skimming pT cut as run metadata, once per data frame so it is + // aligned with the derived tables. The auto-injected metadata collector merges + // all META messages (oldest-possible completion) and the AOD writer stores the + // result as the metaData object of the derived AO2D file. + specs.push_back(DataProcessorSpec{ + .name = "skim-metadata", + .inputs = {InputSpec{"tfn", "TFN", "TFNumber"}}, + .outputs = {OutputSpec{{"meta"}, "META", "SKIMINFO", 0, Lifetime::Sporadic}}, + .algorithm = adaptStateless([](ProcessingContext& pc) { + TMap m; + m.SetOwnerKeyValue(); + m.Add(new TObjString("SkimTrackPtCut"), new TObjString(TString::Format("%g", kSkimPtCut))); + pc.outputs().snapshot(Output{"META", "SKIMINFO", 0}, m); + }), + }); + return specs; } diff --git a/GPU/Workflow/src/GPUWorkflowITS.cxx b/GPU/Workflow/src/GPUWorkflowITS.cxx index ac9834d3eacd1..63ddd663a116e 100644 --- a/GPU/Workflow/src/GPUWorkflowITS.cxx +++ b/GPU/Workflow/src/GPUWorkflowITS.cxx @@ -23,6 +23,8 @@ #include "CommonUtils/NameConf.h" #include "ITStracking/TrackingInterface.h" #include "ITStracking/TrackingConfigParam.h" +#include +#include #ifdef ENABLE_UPGRADES #include "ITS3Reconstruction/TrackingInterface.h" @@ -38,8 +40,15 @@ int32_t GPURecoWorkflowSpec::runITSTracking(o2::framework::ProcessingContext& pc mITSTrackingInterface->run(pc); static bool first = true; if (mNTFs == 1 && pc.services().get().inputTimesliceId == 0) { - o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, o2::its::VertexerParamConfig::Instance().getName()), o2::its::VertexerParamConfig::Instance().getName()); - o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, o2::its::TrackerParamConfig::Instance().getName()), o2::its::TrackerParamConfig::Instance().getName()); + const auto& vtconf = o2::its::VertexerParamConfig::Instance(); + const auto& trconf = o2::its::TrackerParamConfig::Instance(); + o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, vtconf.getName()), vtconf.getName()); + o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, trconf.getName()), trconf.getName()); + TMap md; + md.SetOwnerKeyValue(); + md.Add(new TObjString(vtconf.getName().c_str()), new TObjString(o2::conf::ConfigurableParam::asJSON(vtconf.getName()).c_str())); + md.Add(new TObjString(trconf.getName().c_str()), new TObjString(o2::conf::ConfigurableParam::asJSON(trconf.getName()).c_str())); + pc.outputs().snapshot(o2::framework::Output{"META", "ITSTRACKER", 0}, md); } return 0; } diff --git a/GPU/Workflow/src/GPUWorkflowSpec.cxx b/GPU/Workflow/src/GPUWorkflowSpec.cxx index d928cfdd502c9..890b7667cdd6a 100644 --- a/GPU/Workflow/src/GPUWorkflowSpec.cxx +++ b/GPU/Workflow/src/GPUWorkflowSpec.cxx @@ -14,6 +14,9 @@ /// @since 2018-04-18 /// @brief Processor spec for running TPC CA tracking +#include +#include +#include "GPUO2ConfigurableParam.h" #include "GPUWorkflow/GPUWorkflowSpec.h" #include "Headers/DataHeader.h" #include "Framework/WorkflowSpec.h" // o2::framework::mergeInputs @@ -807,6 +810,15 @@ void GPURecoWorkflowSpec::run(ProcessingContext& pc) } if (mNTFs == 1 && pc.services().get().inputTimesliceId == 0) { // TPC ConfigurableCarams are somewhat special, need to construct by hand o2::conf::ConfigurableParam::write(o2::base::NameConf::getConfigOutputFileName(pc.services().get().name, "rec_tpc"), "GPU_rec_tpc,GPU_rec,GPU_proc_param,GPU_proc,GPU_global,trackTuneParams"); + TMap md; + md.SetOwnerKeyValue(); + md.Add(new TObjString(o2::gpu::internal::GPUConfigurableParamGPUSettingsRecTPC::Instance().getName().c_str()), new TObjString(o2::conf::ConfigurableParam::asJSON(o2::gpu::internal::GPUConfigurableParamGPUSettingsRecTPC::Instance().getName()).c_str())); + md.Add(new TObjString(o2::gpu::internal::GPUConfigurableParamGPUSettingsRec::Instance().getName().c_str()), new TObjString(o2::conf::ConfigurableParam::asJSON(o2::gpu::internal::GPUConfigurableParamGPUSettingsRec::Instance().getName()).c_str())); + md.Add(new TObjString(o2::gpu::internal::GPUConfigurableParamGPUSettingsProcessingParam::Instance().getName().c_str()), new TObjString(o2::conf::ConfigurableParam::asJSON(o2::gpu::internal::GPUConfigurableParamGPUSettingsProcessingParam::Instance().getName()).c_str())); + md.Add(new TObjString(o2::gpu::internal::GPUConfigurableParamGPUSettingsProcessing::Instance().getName().c_str()), new TObjString(o2::conf::ConfigurableParam::asJSON(o2::gpu::internal::GPUConfigurableParamGPUSettingsProcessing::Instance().getName()).c_str())); + md.Add(new TObjString(o2::gpu::internal::GPUConfigurableParamGPUSettingsO2::Instance().getName().c_str()), new TObjString(o2::conf::ConfigurableParam::asJSON(o2::gpu::internal::GPUConfigurableParamGPUSettingsO2::Instance().getName()).c_str())); + md.Add(new TObjString(o2::globaltracking::TrackTuneParams::Instance().getName().c_str()), new TObjString(o2::conf::ConfigurableParam::asJSON(o2::globaltracking::TrackTuneParams::Instance().getName()).c_str())); + pc.outputs().snapshot(Output{"META", "TPCTRACKER", 0}, md); } std::unique_ptr ptrsDump; @@ -1368,6 +1380,7 @@ Outputs GPURecoWorkflowSpec::outputs() if (mSpecConfig.outputErrorQA) { outputSpecs.emplace_back(gDataOriginGPU, "ERRORQA", 0, Lifetime::Timeframe); } + outputSpecs.emplace_back("META", "TPCTRACKER", 0, Lifetime::Sporadic); if (mSpecConfig.runITSTracking) { outputSpecs.emplace_back(gDataOriginITS, "TRACKS", 0, Lifetime::Timeframe); @@ -1382,6 +1395,7 @@ Outputs GPURecoWorkflowSpec::outputs() outputSpecs.emplace_back(gDataOriginITS, "VERTICESMCPUR", 0, Lifetime::Timeframe); outputSpecs.emplace_back(gDataOriginITS, "TRACKSMCTR", 0, Lifetime::Timeframe); } + outputSpecs.emplace_back("META", "ITSTRACKER", 0, Lifetime::Sporadic); } return outputSpecs;