Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions Framework/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,8 @@ set(EXE_SRCS
src/runPostProcessing.cxx
src/runPostProcessingOCC.cxx
src/runLocationCalculator.cxx
src/runMergerCalculator.cxx)
src/runMergerCalculator.cxx
src/runUploadRootObjects.cxx)

set(EXE_NAMES
o2-qc-run-producer
Expand All @@ -149,7 +150,8 @@ set(EXE_NAMES
o2-qc-run-postprocessing
o2-qc-run-postprocessing-occ
o2-qc-location-calculator
o2-qc-merger-calculator)
o2-qc-merger-calculator
o2-qc-upload-root-objects)

# These were the original names before the convention changed. We will get rid
# of them but for the time being we want to create symlinks to avoid confusion.
Expand All @@ -166,7 +168,8 @@ set(EXE_OLD_NAMES
qcRunPostProcessing
qcRunPostProcessingOCC
o2-qc-location-calculator
o2-qc-merger-calculator)
o2-qc-merger-calculator
o2-qc-upload-root-objects)

# As per https://stackoverflow.com/questions/35765106/symbolic-links-cmake
macro(install_symlink filepath sympath)
Expand Down
146 changes: 146 additions & 0 deletions Framework/src/runUploadRootObjects.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
// All rights not expressly granted are reserved.
//
// This software is distributed under the terms of the GNU General Public
// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
//
// In applying this license CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.

///
/// \file runUploadRootObjects.cxx
/// \author Piotr Konopka
///
/// \brief This is an executable which reads ROOT files with objects and puts them to QCDB.
///
/// This is an executable which reads QAResults.root generated by DPL analysis tasks and puts them to QCDB.
/// It will ignore the directory structure and put all objects in under the task name specified as the argument.
/// By default the current date and time will be used as the start of validity, and the object will be valid for 10 years.

#include "QualityControl/QcInfoLogger.h"
#include "QualityControl/CcdbDatabase.h"
#include "QualityControl/MonitorObject.h"

#include <functional>
#include <string>
#include <boost/program_options.hpp>
#include <boost/exception/diagnostic_information.hpp>
#include <TFile.h>
#include <TKey.h>

namespace bpo = boost::program_options;
using namespace o2::quality_control::core;
using namespace o2::quality_control::repository;

int main(int argc, const char* argv[])
{
size_t objectsUploaded = 0;

try {
bpo::options_description desc{ "Options" };
desc.add_options() //
("help,h", "Help screen") //
("input-file", bpo::value<std::string>()->default_value("./QAResults.root"), "Path to the ROOT file with objects to insert.") //
("qcdb-url", bpo::value<std::string>()->default_value("ccdb-test.cern.ch:8080"), "URL to the QCDB.") //
("task-name", bpo::value<std::string>(), "Name of the task to which the objects belong. Use / to make directories") //
("detector-code", bpo::value<std::string>()->default_value("TST"), "3-letter detector code. Put AOD for analysis tasks") //
("validity-start", bpo::value<uint64_t>()->default_value(0), "Start of objects validity in ms since epoch") //
("validity-end", bpo::value<uint64_t>()->default_value(0), "End of objects validity in ms since epoch") //
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you actually want default values here, in particular 0 ?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For validity and filters: I don't think there is a good way to retrieve those values from analysis tasks, so I would not bother people too much about it before we can offer a solution.
For run number: indeed we could require an explicitly stated value, I will fix this. If it is multi-run, people should be aware of it and put 0 then.

("run-number", bpo::value<uint64_t>(), "Run number of objects (put 0 for many runs)") //
("period-name", bpo::value<std::string>()->default_value("unknown"), "Period name of the objects") // todo one could ask logbook
("pass-name", bpo::value<std::string>()->default_value("unknown"), "Calib/reco/sim pass name") //
("provenance", bpo::value<std::string>()->default_value("qc"), "Object path prefix used to mark if data comes from detector (use qc) or simulation (use qc_mc)");

bpo::variables_map vm;
store(parse_command_line(argc, argv, desc), vm);
notify(vm);

if (vm.count("help")) {
// no infologger here, because the message is too long.
std::cout << desc << std::endl;
return 0;
}

/// Read and validate arguments
auto inputFilePath = vm["input-file"].as<std::string>();
auto qcdbUrl = vm["qcdb-url"].as<std::string>();
auto taskName = vm["task-name"].as<std::string>();
auto detectorCode = vm["detector-code"].as<std::string>();
auto validityStart = vm["validity-start"].as<uint64_t>();
auto validityEnd = vm["validity-end"].as<uint64_t>();
auto runNumber = vm["run-number"].as<uint64_t>();
auto periodName = vm["period-name"].as<std::string>();
auto passName = vm["pass-name"].as<std::string>();
auto provenance = vm["provenance"].as<std::string>();

if (validityStart == 0) {
validityStart = CcdbDatabase::getCurrentTimestamp();
}
if (validityEnd == 0) {
validityEnd = validityStart + 1000ull * 60 * 60 * 24 * 365 * 10;
}
if (validityStart > validityEnd) {
throw std::runtime_error("Validity start (" + std::to_string(validityStart) + ") is further in the future than validity end (" + std::to_string(validityEnd) + ")");
}
if (provenance != "qc" && provenance != "qc_mc") {
throw std::runtime_error("Provenance should be either 'qc' (for data from detector) or 'qc_mc' (for simulated data), while '" + provenance + "' was given.");
}

/// Open ROOT file
auto* file = new TFile(inputFilePath.c_str(), "READ");
if (file->IsZombie()) {
throw std::runtime_error("File '" + inputFilePath + "' is zombie.");
}
if (!file->IsOpen()) {
throw std::runtime_error("Failed to open the file: " + inputFilePath);
}
ILOG(Info) << "Input file '" << inputFilePath << "' successfully open." << ENDM;

/// Open CCDB interface
CcdbDatabase database;
database.connect(qcdbUrl, "", "", "");

/// Upload the objects
std::function<void(TDirectoryFile*)> browseFileAndUpload = [&](TDirectoryFile* directory) {
TIter next(directory->GetListOfKeys());
TKey* key;
while ((key = (TKey*)next())) {
auto storedTObj = directory->Get(key->GetName());
if (storedTObj != nullptr) {
if (storedTObj->InheritsFrom("TDirectoryFile")) {
browseFileAndUpload(dynamic_cast<TDirectoryFile*>(storedTObj));
} else {
std::shared_ptr<MonitorObject> mo = std::make_shared<MonitorObject>(storedTObj, taskName, "unknown", detectorCode, runNumber, periodName, passName, provenance);
mo->setIsOwner(false);
database.storeMO(mo, validityStart, validityEnd);
objectsUploaded++;
}
}
delete storedTObj;
}
};

browseFileAndUpload(file);

file->Close();
delete file;

database.disconnect();

} catch (const bpo::error& ex) {
ILOG(Error, Ops) << "Exception caught: " << ex.what() << ENDM;
return 1;
} catch (const boost::exception& ex) {
ILOG(Error, Ops) << "Exception caught: " << boost::current_exception_diagnostic_information(true) << ENDM;
return 1;
}

if (objectsUploaded > 0) {
ILOG(Info, Support) << "Successfully uploaded " << objectsUploaded << " objects to the QCDB." << ENDM;
} else {
ILOG(Info, Support) << "No objects were uploaded to the QCDB. Maybe the file is empty?" << ENDM;
}
return 0;
}
44 changes: 39 additions & 5 deletions doc/Advanced.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ Advanced topics
* [Moving window](#moving-window)
* [Writing a DPL data producer](#writing-a-dpl-data-producer)
* [QC with DPL Analysis](#qc-with-dpl-analysis)
* [Getting AODs directly](#getting-aods-directly)
* [Uploading objects to QCDB](#uploading-objects-to-qcdb)
* [Getting AODs in QC Tasks](#getting-aods-in-qc-tasks)
* [Merging with other analysis workflows](#merging-with-other-analysis-workflows)
* [Enabling a workflow to run on Hyperloop](#enabling-a-workflow-to-run-on-hyperloop)
* [CCDB / QCDB](#ccdb--qcdb)
Expand Down Expand Up @@ -368,15 +369,48 @@ You will probably write it in your detector's O2 directory rather than in the QC

## QC with DPL Analysis

It is possible to attach QC to the Run 3 Analysis Tasks, as they use Data Processing Layer, just as
QC. AOD tables can be requested as direct data sources and then read by a QC task with
TableConsumer. One can also request AOD tables directly from an AOD file.
QC offers several ways to interact with the DPL Analysis framework.
One allows to [upload root objects generated](#uploading-objects-to-qcdb) by an Analysis Task into QCDB.
It is also possible to [run QC workflows alongside Analysis Tasks](#merging-with-other-analysis-workflows),
as they are also based on the Data Processing Layer.
AOD tables can be [requested as direct data sources](#getting-aods-in-qc-tasks) and then read by a QC task with TableConsumer.

In this piece of documentation it is assumed that the users already have some idea about QC and
[DPL Analysis](https://aliceo2group.github.io/analysis-framework), and
they have an access to AOD files following the Run 3 data model.

### Getting AODs directly
### Uploading objects to QCDB

To upload objects written to a file by an Analysis Task to QCDB, one may use the following command:
```shell script
o2-qc-upload-root-objects \
--input-file ./QAResults.root \
--qcdb-url ccdb-test.cern.ch:8080 \
--task-name AnalysisFromFileTest \
--detector-code TST \
--provenance qc_mc \
--pass-name passMC \
--period-name SimChallenge \
--run-number 49999
```

See the `--help` message for explanation of the arguments.
If everything went well, the objects should be accessible in [the test QCG instance](https://qcg-test.cern.ch) under
the directories listed in the logs:
```
2021-10-05 10:59:41.408998 QC infologger initialized
2021-10-05 10:59:41.409053 Input file './QAResults.root' successfully open.
...
2021-10-05 10:59:41.585893 Storing MonitorObject qc_mc/TST/MO/AnalysisFromFileTest/hMcEventCounter
2021-10-05 10:59:41.588649 Storing MonitorObject qc_mc/TST/MO/AnalysisFromFileTest/hGlobalBcFT0
2021-10-05 10:59:41.591542 Storing MonitorObject qc_mc/TST/MO/AnalysisFromFileTest/hTimeT0Aall
2021-10-05 10:59:41.594386 Storing MonitorObject qc_mc/TST/MO/AnalysisFromFileTest/hTimeT0Call
2021-10-05 10:59:41.597743 Successfully uploaded 10 objects to the QCDB.
```
Notice that the executable will ignore the directory structure in the input file and upload all objects to one directory.
If you need a different behaviour, please contact the developers.

### Getting AODs in QC Tasks

First, let's see how to get data directly from an AOD file. To read the table, we will use TableConsumer from DPL, as in [the example of a QC analysis
task](../Modules/Example/src/AnalysisTask.cxx):
Expand Down