150 std::string parentFileReplacement;
151 if (ctx.options().isSet(
"aod-parent-base-path-replacement")) {
152 parentFileReplacement = ctx.options().get<std::string>(
"aod-parent-base-path-replacement");
154 int parentAccessLevel = 0;
155 if (ctx.options().isSet(
"aod-parent-access-level")) {
156 parentAccessLevel = ctx.options().get<
int>(
"aod-parent-access-level");
158 std::vector<std::pair<std::string, int>> originLevelMapping;
159 if (ctx.options().isSet(
"aod-origin-level-mapping")) {
160 auto originLevelMappingStr = ctx.options().get<std::string>(
"aod-origin-level-mapping");
161 for (
auto pairRange : originLevelMappingStr | std::views::split(
',')) {
162 std::string_view pair{pairRange.begin(), pairRange.end()};
163 auto colonPos = pair.find(
':');
164 if (colonPos == std::string_view::npos) {
165 LOGP(fatal,
"Badly formatted aod-origin-level-mapping entry: \"{}\"", pair);
168 std::string
key(pair.substr(0, colonPos));
169 std::string_view valueStr = pair.substr(colonPos + 1);
171 auto [
ptr, ec] = std::from_chars(valueStr.data(), valueStr.data() + valueStr.size(),
value);
172 if (ec == std::errc{}) {
173 originLevelMapping.emplace_back(std::move(
key),
value);
175 LOGP(fatal,
"Unable to parse level in aod-origin-level-mapping entry: \"{}\"", pair);
192 if (!options.
isSet(
"aod-file-private")) {
193 LOGP(fatal,
"No input file defined!");
194 throw std::runtime_error(
"Processing is stopped!");
197 auto filename = options.
get<std::string>(
"aod-file-private");
199 auto maxRate = options.
get<
float>(
"aod-max-io-rate");
204 if (context.readerBackend !=
"root" && context.readerBackend !=
"native" && context.readerBackend !=
"auto") {
205 LOGP(fatal,
"Invalid --aod-reader-backend \"{}\": use root, native or auto.", context.readerBackend);
207 context.nativeOptions.threads = std::max(1, options.
get<
int>(
"aod-reader-threads"));
208 context.nativeOptions.prefetchDepth = std::max(0, options.
get<
int>(
"aod-reader-prefetch"));
209 if (
auto mb = options.
get<
int>(
"aod-reader-buffer-mb"); mb >= 0) {
210 context.nativeOptions.bufferBudget = mb *
size_t{1024 * 1024};
212 auto localIO = options.
get<std::string>(
"aod-reader-local-io");
213 if (localIO !=
"mmap" && localIO !=
"pread") {
214 LOGP(fatal,
"Invalid --aod-reader-local-io \"{}\": use mmap or pread.", localIO);
216 context.nativeOptions.localIO = localIO ==
"pread" ? o2::aod::native::Options::LocalIO::PRead : o2::aod::native::Options::LocalIO::MMap;
217 auto didir = std::make_shared<DataInputDirector>(std::vector<std::string>{
filename}, std::move(context));
218 if (options.
isSet(
"aod-reader-json")) {
219 auto jsonFile = options.
get<std::string>(
"aod-reader-json");
220 if (!didir->readJson(jsonFile)) {
221 LOGP(
error,
"Check the JSON document! Can not be properly parsed!");
230 bool reportTFN =
false;
231 bool reportTFFileName =
false;
234 std::vector<OutputRoute> requestedTables;
236 for (
auto route :
routes) {
239 TFNumberHeader =
header::DataHeader(concrete.description, concrete.origin, concrete.subSpec);
243 TFFileNameHeader =
header::DataHeader(concrete.description, concrete.origin, concrete.subSpec);
244 reportTFFileName =
true;
246 requestedTables.emplace_back(route);
249 int level = originLevelMapping.empty() ? -1 : 0;
250 auto fileCounter = std::make_shared<int>(0);
251 auto numTF = std::make_shared<int>(-1);
252 bool const skipInvalidReads = shouldSkipInvalidReads();
261 didir, reportTFN, reportTFFileName,
level](
Monitoring& monitoring,
DataAllocator& outputs,
ControlService& control,
DeviceSpec const& device,
DataProcessingStats& dpstats,
ArrowContext& arrowContext,
MessageContext& messageContext,
StringContext& stringContext) {
266 int ntf = *numTF + 1;
267 static int currentFileCounter = -1;
268 static int filesProcessed = 0;
269 if (currentFileCounter != *fileCounter) {
270 currentFileCounter = *fileCounter;
271 monitoring.send(
Metric{(uint64_t)++filesProcessed,
"files-opened"}.addTag(Key::Subsystem, monitoring::tags::Value::DPL));
275 static size_t totalSizeUncompressed = 0;
276 static size_t totalSizeCompressed = 0;
277 static uint64_t totalDFSent = 0;
278 static uint64_t totalInvalidReadSkipped = 0;
279 static uint64_t totalFileOpenSkipped = 0;
282 if (!watchdog->update()) {
283 LOGP(info,
"Run time exceeds run time limit of {} seconds. Exiting gracefully...", watchdog->runTimeLimit);
284 LOGP(info,
"Stopping reader {} after time frame {}.", device.
inputTimesliceId, watchdog->numberTimeFrames - 1);
285 didir->closeInputFiles();
286 monitoring.flushBuffer();
292 int64_t startTime = uv_hrtime();
293 int64_t startSize = totalSizeCompressed;
294 auto discardTimeframe = [&didir, &arrowContext, &messageContext, &stringContext]() {
296 didir->finishTimeFrame(
true);
297 arrowContext.clear();
298 messageContext.discard();
299 stringContext.
clear();
301 auto skipInvalidRead = [&fcnt, &ntf, &discardTimeframe, &dpstats, &fileCounter, &numTF, &device](
ConcreteDataMatcher const& concrete,
AODReadError const& e) {
302 auto skippedTimeframes = ++totalInvalidReadSkipped;
303 LOGP(
error,
"Invalid AOD read for table {}: fileCounter {}, timeFrame {}. Skipping timeframe (skipped timeframes: {}). Reason: {}",
304 concrete.origin.as<std::string>(), fcnt, ntf, skippedTimeframes, describeException(e));
310 enum class TFReaderState {
312 READ_FIRST_TABLE_FROM_NEXT_FILE,
319 auto readState = TFReaderState::READ_FIRST_TABLE;
320 [[maybe_unused]]
auto stateName = [](TFReaderState
state) ->
char const* {
322 case TFReaderState::READ_FIRST_TABLE:
323 return "READ_FIRST_TABLE";
324 case TFReaderState::READ_FIRST_TABLE_FROM_NEXT_FILE:
325 return "READ_FIRST_TABLE_FROM_NEXT_FILE";
326 case TFReaderState::READ_NEXT_TABLE:
327 return "READ_NEXT_TABLE";
328 case TFReaderState::TRY_NEXT_FILE:
329 return "TRY_NEXT_FILE";
330 case TFReaderState::TIMEFRAME_READ:
331 return "TIMEFRAME_READ";
332 case TFReaderState::INVALID_TIMEFRAME:
333 return "INVALID_TIMEFRAME";
334 case TFReaderState::INVALID_FILE:
335 return "INVALID_FILE";
340 auto transitionTo = [&](TFReaderState nextState) {
342 "%{public}s -> %{public}s (fileCounter %d, timeFrame %d)",
343 stateName(readState), stateName(nextState), fcnt, ntf);
344 readState = nextState;
346 size_t routeIndex = 0;
347 auto reportTimeframe = [&didir, &fcnt, &ntf, &outputs, &TFNumberHeader, &TFFileNameHeader, reportTFN, reportTFFileName](
header::DataHeader const& dh) {
350 auto timeFrameNumber = didir->getTimeFrameNumber(dh, fcnt, ntf);
351 auto o =
Output(TFNumberHeader);
352 outputs.make<uint64_t>(o) = timeFrameNumber;
355 if (reportTFFileName) {
358 auto fileAndFolder = didir->getFileFolder(dh, fcnt, ntf);
359 static std::string pwd = gSystem->pwd() + std::string(
"/");
360 std::string currentFilename;
361 if (
auto nativeFS = std::dynamic_pointer_cast<NativeAODFileSystem>(fileAndFolder.filesystem())) {
362 currentFilename = nativeFS->name();
364 if (!currentFilename.starts_with(
"/") && currentFilename.find(
"://") == std::string::npos) {
365 currentFilename = pwd + currentFilename;
368 auto rootFS = std::dynamic_pointer_cast<TFileFileSystem>(fileAndFolder.filesystem());
369 auto*
f =
dynamic_cast<TFile*
>(rootFS->GetFile());
370 currentFilename =
f->GetFile()->GetName();
371 if (strcmp(
f->GetEndpointUrl()->GetProtocol(),
"file") == 0 &&
f->GetEndpointUrl()->GetFile()[0] !=
'/') {
373 currentFilename = pwd + std::string(
f->GetName());
376 outputs.make<std::string>(
o2) = currentFilename;
379 auto tryReadTable = [&device, &didir, &fcnt, &ntf, &outputs, &reportTimeframe, &requestedTables, &routeIndex, &skipInvalidRead, &discardTimeframe, &dpstats, skipInvalidReads](TFReaderState currentState) -> TFReaderState {
380 while (routeIndex < requestedTables.size() &&
381 (device.
inputTimesliceId % requestedTables[routeIndex].maxTimeslices) != requestedTables[routeIndex].timeslice) {
384 if (routeIndex == requestedTables.size()) {
385 return TFReaderState::TIMEFRAME_READ;
388 auto& route = requestedTables[routeIndex];
390 auto dh =
header::DataHeader(concrete.description, concrete.origin, concrete.subSpec);
391 bool wasAOD = std::ranges::any_of(route.matcher.metadata, [](
ConfigParamSpec const& p) { return p.name.starts_with(
"aod-origin-replaced"); });
393 if (currentState == TFReaderState::READ_FIRST_TABLE || currentState == TFReaderState::READ_FIRST_TABLE_FROM_NEXT_FILE) {
394 didir->beginTimeFrame();
397 if (!didir->readTree(outputs, dh, fcnt, ntf, totalSizeCompressed, totalSizeUncompressed, wasAOD)) {
398 return TFReaderState::TRY_NEXT_FILE;
401 if (!skipInvalidReads) {
406 skipInvalidRead(concrete, e);
407 return TFReaderState::INVALID_TIMEFRAME;
409 auto skippedFiles = ++totalFileOpenSkipped;
410 LOGP(
error,
"Unable to open AOD input for table {}: fileCounter {}, timeFrame {}. Skipping file (skipped files: {}). Reason: {}",
411 concrete.origin.as<std::string>(), fcnt, ntf, skippedFiles, describeException(e));
414 return TFReaderState::INVALID_FILE;
420 if (currentState == TFReaderState::READ_FIRST_TABLE || currentState == TFReaderState::READ_FIRST_TABLE_FROM_NEXT_FILE) {
424 return TFReaderState::READ_NEXT_TABLE;
426 while (readState != TFReaderState::TIMEFRAME_READ) {
428 case TFReaderState::READ_FIRST_TABLE:
429 transitionTo(tryReadTable(readState));
431 case TFReaderState::READ_FIRST_TABLE_FROM_NEXT_FILE:
432 case TFReaderState::READ_NEXT_TABLE:
433 transitionTo(tryReadTable(readState));
434 if (readState == TFReaderState::TRY_NEXT_FILE) {
437 LOGP(fatal,
"Can not retrieve tree for table {}: fileCounter {}, timeFrame {}", concrete.origin.as<std::string>(), fcnt, ntf);
438 throw std::runtime_error(
"Processing is stopped!");
441 case TFReaderState::INVALID_FILE:
443 messageContext.clear();
445 case TFReaderState::TRY_NEXT_FILE:
446 didir->finishTimeFrame();
448 if (didir->atEnd(fcnt)) {
449 LOGP(info,
"No input files left to read for reader {}!", device.
inputTimesliceId);
450 didir->closeInputFiles();
451 monitoring.flushBuffer();
458 transitionTo(TFReaderState::READ_FIRST_TABLE_FROM_NEXT_FILE);
460 case TFReaderState::INVALID_TIMEFRAME:
462 messageContext.clear();
465 transitionTo(TFReaderState::READ_FIRST_TABLE);
467 case TFReaderState::TIMEFRAME_READ:
471 didir->finishTimeFrame();
472 int64_t stopSize = totalSizeCompressed;
473 int64_t bytesDelta = stopSize - startSize;
474 int64_t stopTime = uv_hrtime();
475 float currentDelta = float(stopTime - startTime) / 1000000000;
476 if (ceil(maxRate) > 0.) {
477 float extraTime = (bytesDelta / 1000000 - currentDelta * maxRate) / maxRate;
479 if (extraTime > 0.) {
480 LOGP(info,
"Read {} MB in {} s. Sleeping for {} seconds to stay within {} MB/s limit.", bytesDelta / 1000000, currentDelta, extraTime, maxRate);
481 uv_sleep(extraTime * 1000);
488 dpstats.processCommandQueue();
489 monitoring.send(
Metric{(uint64_t)totalDFSent,
"df-sent"}.addTag(Key::Subsystem, monitoring::tags::Value::DPL));
490 monitoring.send(
Metric{(uint64_t)totalSizeUncompressed / 1000,
"aod-bytes-read-uncompressed"}.addTag(Key::Subsystem, monitoring::tags::Value::DPL));
491 monitoring.send(
Metric{(uint64_t)totalSizeCompressed / 1000,
"aod-bytes-read-compressed"}.addTag(Key::Subsystem, monitoring::tags::Value::DPL));
501 auto firstRoute = std::ranges::find_if(requestedTables, [&didir,
level](
auto const& route) {
503 return didir->getLevelForOrigin(concrete.origin) ==
level;
506 auto dh =
header::DataHeader(concrete.description, concrete.origin, concrete.subSpec);
507 auto fileAndFolder = didir->getFileFolder(dh, fcnt, ntf);
510 if (fileAndFolder.filesystem() ==
nullptr) {
513 if (didir->atEnd(fcnt)) {
514 LOGP(info,
"No input files left to read for reader {}!", device.
inputTimesliceId);
515 didir->closeInputFiles();
516 monitoring.flushBuffer();