41#include <fmt/format.h>
42#include <fmt/ranges.h>
53#include <unordered_set>
61void customize(std::vector<ConfigParamSpec>& workflowOptions)
64 std::vector<ConfigParamSpec> options{
65 {
"crus", VariantType::String, cruDefault.c_str(), {
"List of CRUs, comma-separated ranges, e.g. 0-3,7,9-15"}},
66 {
"timeframes", VariantType::Int, 100, {
"Number of TFs to generate; use -1 to run indefinitely"}},
67 {
"delay", VariantType::Bool,
false, {
"Add delay after sending all CRUs"}},
68 {
"delayTime", VariantType::Int, 1, {
"Duration of the global per-TF delay in ms (requires --delay true)"}},
69 {
"delayEveryN", VariantType::Int, 1, {
"Apply the global delay only on average once every N TFs, randomly chosen (1 = every TF, requires --delay true)"}},
70 {
"delayCRUs", VariantType::String,
"", {
"CRUs for which to add an extra per-CRU delay before sending, comma-separated ranges"}},
71 {
"delayTimeCRUs", VariantType::Int, 1, {
"Duration of the per-CRU delay in ms (requires --delayCRUs)"}},
72 {
"dropTFsRandom", VariantType::Int, 0, {
"Drop a whole TF randomly: on average one every N TFs (0 = disabled)"}},
73 {
"dropTFsRange", VariantType::String,
"", {
"Drop all TFs in this range, e.g. 10-12"}},
74 {
"tfLength", VariantType::Float, 0.f, {
"Minimum wall-clock time between consecutively sent TFs in ms (rate limiter); the generator sleeps if a TF is produced faster than this (0 = disabled)"}},
75 {
"seed", VariantType::Int, 42, {
"RNG seed for CMV value generation"}},
76 {
"amplitude", VariantType::Float, 5.0f, {
"Amplitude of the sinusoidal CMV signal (ADC units); ignored when --input-file is set"}},
77 {
"noise", VariantType::Float, 1.0f, {
"Gaussian noise std-dev added per time bin (ADC units); used as the smearing width in --input-file mode"}},
78 {
"input-file", VariantType::String,
"", {
"ROOT file with a CMV 'ccdb_object' tree; the template TF (see --input-entry) is decoded once and re-emitted, smeared per generated TF. Empty = synthetic sinusoidal signal"}},
79 {
"input-entry", VariantType::Int, 0, {
"Tree entry (TF index) used as the template when --input-file is set"}},
80 {
"configKeyValues", VariantType::String,
"", {
"Semicolon separated key=value strings"}}};
82 std::swap(workflowOptions, options);
96 const std::unordered_set<uint32_t>& delayCRUs,
103 const std::vector<int>& rangeTFsDrop,
108 const std::string& inputFile,
109 long long inputEntry)
110 : mCRUs(crus), mDelayCRUs(delayCRUs), mMaxTFs(maxTFs), mDelay(delay), mDelayTime(delayTime), mDelayEveryN(delayEveryN), mDelayTimeCRUs(delayTimeCRUs), mDropTFsRandom(dropTFsRandom), mRangeTFsDrop(rangeTFsDrop), mTFLength(tfLength), mAmplitude(amplitude), mNoise(noise), mRng(static_cast<
std::mt19937::result_type>(seed)), mInputFileName(inputFile), mInputEntry(inputEntry) {}
114 mTimer100TFs = std::chrono::high_resolution_clock::now();
115 mLastTFTime = std::chrono::high_resolution_clock::now();
117 if (!mCRUs.empty()) {
118 LOGP(info,
"crus: {}", fmt::join(mCRUs,
", "));
120 if (!mDelayCRUs.empty()) {
121 const std::vector<uint32_t> delayCRUsSorted(mDelayCRUs.begin(), mDelayCRUs.end());
122 LOGP(info,
"delayCRUs: {}", fmt::join(delayCRUsSorted,
", "));
125 mWriteDebug = ic.options().get<
bool>(
"write-debug");
127 mDebugStreamFileName = ic.options().get<std::string>(
"debug-file-name");
128 LOGP(info,
"Creating debug stream {}", mDebugStreamFileName);
129 mDebugStream = std::make_unique<o2::utils::TreeStreamRedirector>(mDebugStreamFileName.data(),
"recreate");
132 if (!mInputFileName.empty()) {
134 if (!handle.
open(mInputFileName)) {
135 throw std::runtime_error(
"CMV generator: failed to open input file " + mInputFileName);
137 const auto nEntries = handle.
tree->GetEntries();
138 if (mInputEntry < 0 || mInputEntry >= nEntries) {
139 const auto msg = fmt::format(
"CMV generator: --input-entry {} out of range [0, {}) in {}", mInputEntry, nEntries, mInputFileName);
141 throw std::runtime_error(
msg);
146 throw std::runtime_error(
"CMV generator: failed to read/decode entry from " + mInputFileName);
151 const bool addNoise = (mNoise > 0.f);
153 mBaseCMVFloat.resize(mCRUs.size());
154 for (
size_t iCRU = 0; iCRU < mCRUs.size(); ++iCRU) {
155 const auto cru = mCRUs[iCRU];
156 auto& base = mBaseCMVFloat[iCRU];
157 base.resize(o2::tpc::cmv::NTimeBinsPerTF);
158 for (uint32_t tb = 0; tb < o2::tpc::cmv::NTimeBinsPerTF; ++tb) {
159 base[tb] = tmpl->
getCMVFloat(
static_cast<int>(cru),
static_cast<int>(tb));
163 mBaseCMVEncoded.resize(mCRUs.size());
164 for (
size_t iCRU = 0; iCRU < mCRUs.size(); ++iCRU) {
165 const auto cru = mCRUs[iCRU];
166 auto& enc = mBaseCMVEncoded[iCRU];
167 enc.resize(o2::tpc::cmv::NTimeBinsPerTF);
168 for (uint32_t tb = 0; tb < o2::tpc::cmv::NTimeBinsPerTF; ++tb) {
176 mUseInputFile =
true;
177 LOGP(info,
"Loaded CMV template from {} (entry {}): {} CRUs x {} bins, noise sigma {} ADC ({})",
178 mInputFileName, mInputEntry, mCRUs.size(), o2::tpc::cmv::NTimeBinsPerTF, mNoise,
179 addNoise ?
"re-smeared per TF" :
"encoded once, replayed verbatim");
185 using timer = std::chrono::high_resolution_clock;
190 if (!mRangeTFsDrop.empty() &&
tf >=
static_cast<uint32_t
>(mRangeTFsDrop.front()) &&
tf <=
static_cast<uint32_t
>(mRangeTFsDrop.back())) {
191 LOGP(info,
"Dropping TF {} (range drop)",
tf);
194 if (mDropTFsRandom > 0 && std::uniform_int_distribution<int>{0, mDropTFsRandom - 1}(mRng) == 0) {
195 LOGP(info,
"Dropping TF {} (random drop)",
tf);
199 auto start = timer::now();
205 const bool addNoise = (mNoise > 0.f);
206 std::normal_distribution<float> noiseDist{0.f, mNoise};
207 std::vector<uint16_t> cmvVec(o2::tpc::cmv::NTimeBinsPerTF);
208 std::vector<float> noiseVec;
211 noiseVec.resize(o2::tpc::cmv::NTimeBinsPerTF);
212 for (
auto&
n : noiseVec) {
217 const float signal = -std::abs(mAmplitude * std::sin(
tf * 0.05f));
218 for (
auto&
v : cmvVec) {
220 d.
setCMVFloat(addNoise ? (signal + noiseDist(mRng)) : signal);
229 std::vector<uint64_t> orbitBCVec(o2::tpc::cmv::NPacketsPerTFPerCRU);
230 for (uint32_t pkt = 0; pkt < o2::tpc::cmv::NPacketsPerTFPerCRU; ++pkt) {
232 orbitBCVec[pkt] = uint64_t(
orbit) << 32;
235 for (
size_t iCRU = 0; iCRU < mCRUs.size(); ++iCRU) {
236 const auto cru = mCRUs[iCRU];
240 if (mDelayCRUs.count(cru)) {
241 LOGP(info,
"Delaying CRU {} by {} ms (TF {})", cru, mDelayTimeCRUs,
tf);
242 std::this_thread::sleep_for(std::chrono::milliseconds(mDelayTimeCRUs));
247 std::vector<uint16_t>* out = &cmvVec;
248 if (mUseInputFile && !addNoise) {
249 out = &mBaseCMVEncoded[iCRU];
250 }
else if (mUseInputFile) {
251 const auto& base = mBaseCMVFloat[iCRU];
252 for (uint32_t tb = 0; tb < o2::tpc::cmv::NTimeBinsPerTF; ++tb) {
259 ctx.outputs().snapshot(
Output{gDataOriginTPC,
"CMVVECTOR", subSpec}, *out);
260 ctx.outputs().snapshot(
Output{gDataOriginTPC,
"CMVORBITS", subSpec}, orbitBCVec);
263 auto&
stream = (*mDebugStream) <<
"cmvs";
265 <<
"tfCounter=" <<
tf
266 <<
"nCMVs=" << out->size()
273 const auto elapsed100 = std::chrono::duration_cast<std::chrono::milliseconds>(timer::now() - mTimer100TFs).count();
274 LOGP(info,
"Generated CMV data for TF {} ({} ms for last 100 TFs)",
tf, elapsed100);
275 mTimer100TFs = timer::now();
279 if (mDelay && (mDelayEveryN <= 1 || std::uniform_int_distribution<int>{0, mDelayEveryN - 1}(mRng) == 0)) {
280 auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(timer::now() -
start).count();
281 if (elapsed < mDelayTime) {
282 LOGP(info,
"Delaying TF {} by {} ms",
tf, mDelayTime - elapsed);
283 std::this_thread::sleep_for(std::chrono::milliseconds(mDelayTime - elapsed));
289 if (mTFLength > 0.f) {
290 const auto elapsedSinceLast = std::chrono::duration_cast<std::chrono::microseconds>(timer::now() - mLastTFTime).count();
291 const auto tfLengthUs =
static_cast<int64_t
>(mTFLength * 1000.f);
292 if (elapsedSinceLast < tfLengthUs) {
293 const auto waitUs = tfLengthUs - elapsedSinceLast;
294 LOGP(info,
"Rate limiting TF {}: waiting {} us (tfLength={} ms)",
tf, waitUs, mTFLength);
295 std::this_thread::sleep_for(std::chrono::microseconds(waitUs));
297 mLastTFTime = timer::now();
301 if (mMaxTFs != std::numeric_limits<unsigned int>::max() &&
tf >= mMaxTFs - 1) {
303 ctx.services().get<
ControlService>().readyToQuit(QuitRequest::Me);
308 void stop() final { closeFiles(); }
314 auto&
stream = (*mDebugStream) <<
"cmvs";
316 tree.SetAlias(
"sector",
"int(cru/10)");
317 mDebugStream->Close();
318 mDebugStream.reset(
nullptr);
322 const std::vector<uint32_t> mCRUs{};
323 const std::unordered_set<uint32_t> mDelayCRUs{};
324 const unsigned int mMaxTFs{};
325 const bool mDelay{
false};
326 const int mDelayTime{1};
327 const int mDelayEveryN{1};
328 const int mDelayTimeCRUs{1};
329 const int mDropTFsRandom{0};
330 const std::vector<int> mRangeTFsDrop{};
331 const float mTFLength{0.f};
332 const float mAmplitude{5.f};
333 const float mNoise{1.f};
335 const std::string mInputFileName{};
336 const long long mInputEntry{0};
337 bool mUseInputFile{
false};
338 std::vector<std::vector<float>> mBaseCMVFloat;
339 std::vector<std::vector<uint16_t>> mBaseCMVEncoded;
340 std::chrono::high_resolution_clock::time_point mTimer100TFs{};
341 std::chrono::high_resolution_clock::time_point mLastTFTime{};
342 bool mWriteDebug{
false};
343 std::string mDebugStreamFileName{};
344 std::unique_ptr<o2::utils::TreeStreamRedirector> mDebugStream{};
349 const std::unordered_set<uint32_t>& delayCRUs,
356 const std::vector<int>& rangeTFsDrop,
361 const std::string& inputFile,
362 long long inputEntry)
364 std::vector<OutputSpec> outputSpecs;
365 outputSpecs.reserve(crus.size() * 2);
366 for (
const auto cru : crus) {
368 outputSpecs.emplace_back(gDataOriginTPC,
"CMVVECTOR", subSpec, Lifetime::Timeframe);
369 outputSpecs.emplace_back(gDataOriginTPC,
"CMVORBITS", subSpec, Lifetime::Timeframe);
376 AlgorithmSpec{adaptFromTask<CMVGeneratorDevice>(crus, delayCRUs, maxTFs, delay, delayTime, delayEveryN, delayTimeCRUs, dropTFsRandom, rangeTFsDrop, tfLength, amplitude, noise, seed, inputFile, inputEntry)},
378 {
"write-debug", VariantType::Bool,
false, {
"Write a debug output tree"}},
379 {
"debug-file-name", VariantType::String,
"./cmv_generator_debug.root", {
"Name of the debug output file"}},
386 const auto tpcCRUs = o2::RangeTokenizer::tokenize<int>(config.
options().
get<std::string>(
"crus"));
387 const std::vector<uint32_t> crus(tpcCRUs.begin(), tpcCRUs.end());
389 const auto delayCRUsStr = config.
options().
get<std::string>(
"delayCRUs");
390 std::unordered_set<uint32_t> delayCRUs;
391 if (!delayCRUsStr.empty()) {
392 for (
const auto cru : o2::RangeTokenizer::tokenize<int>(delayCRUsStr)) {
393 delayCRUs.insert(
static_cast<uint32_t
>(cru));
397 const auto dropTFsRangeStr = config.
options().
get<std::string>(
"dropTFsRange");
398 const auto rangeTFsDrop = dropTFsRangeStr.empty() ? std::vector<int>{} : o2::RangeTokenizer::tokenize<int>(dropTFsRangeStr);
399 const int timeframesInt = config.
options().
get<
int>(
"timeframes");
401 const auto timeframes = (timeframesInt < 0) ? std::numeric_limits<unsigned int>::max() :
static_cast<unsigned int>(timeframesInt);
402 const auto delay = config.
options().
get<
bool>(
"delay");
403 const auto delayTime = config.
options().
get<
int>(
"delayTime");
404 const auto delayEveryN = config.
options().
get<
int>(
"delayEveryN");
405 const auto delayTimeCRUs = config.
options().
get<
int>(
"delayTimeCRUs");
406 const auto dropTFsRandom = config.
options().
get<
int>(
"dropTFsRandom");
407 const auto tfLength = config.
options().
get<
float>(
"tfLength");
408 const auto seed = config.
options().
get<
int>(
"seed");
409 const auto amplitude = config.
options().
get<
float>(
"amplitude");
410 const auto noise = config.
options().
get<
float>(
"noise");
411 const auto inputFile = config.
options().
get<std::string>(
"input-file");
412 const auto inputEntry =
static_cast<long long>(config.
options().
get<
int>(
"input-entry"));
417 workflow.emplace_back(
generateCMVsCRU(crus, delayCRUs, timeframes, delay, delayTime, delayEveryN, delayTimeCRUs, dropTFsRandom, rangeTFsDrop, tfLength, amplitude, noise, seed, inputFile, inputEntry));
420 long startTime = hbfu.startTime > 0 ? hbfu.startTime : std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::system_clock::now()).time_since_epoch().count();
Structs for storing CMVs to the CCDB.
Helper utilities for reading CMV ROOT files.
Common mode values data format definition.
Helper function to tokenize sequences and ranges of integral numbers.
void init(o2::framework::InitContext &ic) final
CMVGeneratorDevice(const std::vector< uint32_t > &crus, const std::unordered_set< uint32_t > &delayCRUs, unsigned int maxTFs, bool delay, int delayTime, int delayEveryN, int delayTimeCRUs, int dropTFsRandom, const std::vector< int > &rangeTFsDrop, float tfLength, float amplitude, float noise, int seed, const std::string &inputFile, long long inputEntry)
static constexpr uint32_t sOrbitsPerPacket
each CMV packet covers 8 heartbeat orbits
void endOfStream(o2::framework::EndOfStreamContext &) final
This is invoked whenever we have an EndOfStream event.
void stop() final
This is invoked on stop.
void run(o2::framework::ProcessingContext &ctx) final
static const HBFUtils & Instance()
static void updateFromString(std::string const &)
ConfigParamRegistry & options() const
T get(const char *key) const
constexpr o2::header::DataOrigin gDataOriginTPC
Defining ITS Vertex explicitly as messageable.
std::vector< DataProcessorSpec > WorkflowSpec
std::vector< InputSpec > Inputs
uint32_t getCurrentTF(o2::framework::ProcessingContext &pc)
std::string to_string(gsl::span< T, Size > span)
std::unique_ptr< GPUReconstructionTimeframe > tf
static void addConfigOption(std::vector< o2::framework::ConfigParamSpec > &opts, const std::string &defOpt=std::string(o2::base::NameConf::DIGITIZATIONCONFIGFILE))
const CMVPerTF * getEntry(long long iEntry)
Load entry iEntry and return a pointer to the decoded CMVPerTF, or nullptr on error.
bool open(const std::string &path)
Open path and set up branch addresses. Returns false on any error.
void close()
Release all resources.
float getCMVFloat(const int cru, const int timeBin) const
Return the float CMV value for a given CRU and timebin within this TF.
CMV single data container.
uint16_t getCMV() const
raw 16-bit integer representation
void setCMVFloat(float value)
DataProcessorSpec generateCMVsCRU(const std::vector< uint32_t > &crus, const std::unordered_set< uint32_t > &delayCRUs, unsigned int maxTFs, bool delay, int delayTime, int delayEveryN, int delayTimeCRUs, int dropTFsRandom, const std::vector< int > &rangeTFsDrop, float tfLength, float amplitude, float noise, int seed, const std::string &inputFile, long long inputEntry)
WorkflowSpec defineDataProcessing(ConfigContext const &config)
This function hooks up the the workflow specifications into the DPL driver.
void customize(std::vector< ConfigParamSpec > &workflowOptions)
std::unique_ptr< TTree > tree((TTree *) flIn.Get(std::string(o2::base::NameConf::CTFTREENAME).c_str()))
uint64_t const void const *restrict const msg