From 3e4b94c4b6c63cfefad5a6e91761558e8f5e9d26 Mon Sep 17 00:00:00 2001 From: Barthelemy Date: Wed, 7 May 2025 15:24:13 +0200 Subject: [PATCH 01/10] [QC-1286] Clean up QuickStart.md --- doc/Advanced.md | 61 +++++++++++++++++++++++ doc/QuickStart.md | 121 ++++++++++++++-------------------------------- 2 files changed, 97 insertions(+), 85 deletions(-) diff --git a/doc/Advanced.md b/doc/Advanced.md index 0eeb3be3f2..c855713526 100644 --- a/doc/Advanced.md +++ b/doc/Advanced.md @@ -2117,6 +2117,67 @@ The values are relative to the canvas size, so in the example above the label wi In consul go to `o2/runtime/aliecs/defaults` and modify the file corresponding to the detector: [det]_qc_shm_segment_size +### Readout chain (optional) + +In this second example, we are going to use the Readout as our data source. +This example assumes that Readout has been compiled beforehand (`aliBuild build Readout --defaults o2`). + +![alt text](images/readout-schema.png) + +This workflow is a bit different from the basic one. The _Readout_ is not a DPL, nor a FairMQ, device and thus we have to have a _proxy_ to get data from it. This is the extra box going to the _Data Sampling_, which then injects data to the task. This is handled in the _Readout_ as long as you enable the corresponding configuration flag. + +The first thing is to load the environment for the readout in a new terminal: `alienv enter Readout/latest`. + +Then enable the data sampling channel in readout by opening the readout config file located at `$READOUT_ROOT/etc/readout-qc.cfg` and make sure that the following properties are correct: + +``` +# Enable the data sampling +[consumer-fmq-qc] +consumerType=FairMQChannel +enableRawFormat=1 +fmq-name=readout-qc +fmq-address=ipc:///tmp/readout-pipe-1 +fmq-type=pub +fmq-transport=zeromq +unmanagedMemorySize=2G +memoryPoolNumberOfPages=500 +memoryPoolPageSize=1M +enabled=1 +(...) +``` + +Start Readout in a terminal: +``` +o2-readout-exe file://$READOUT_ROOT/etc/readout-qc.cfg +``` + +Start in another terminal the proxy, DataSampling and QC workflows: +``` +o2-qc-run-readout | o2-qc --config json://${QUALITYCONTROL_ROOT}/etc/readout.json +``` + +The data sampling is configured to sample 1% of the data as the readout should run by default at full speed. + +#### Getting real data from readout + +See [these instructions for readout](ModulesDevelopment.md#readout) and [these for O2 utilities](ModulesDevelopment.md#dpl-workflow). + +#### Readout data format as received by the Task + +The header is an O2 header populated with data from the header built by the Readout. +The payload received is a 2MB (configurable) data page made of CRU pages (8kB). + +__Configuration file__ + +The configuration file is installed in `$QUALITYCONTROL_ROOT/etc`. Each time you rebuild the code, `$QUALITYCONTROL_ROOT/etc/readout.json` is overwritten by the file in the source directory (`~/alice/QualityControl/Framework/readout.json`). +To avoid this behaviour and preserve the changes you do to the configuration, you can copy the file and specify the path to it with the parameter `--config` when launch `o2-qc`. + +To change the fraction of the data being monitored, change the option `fraction`. + +``` +"fraction": "0.01", +``` + --- [← Go back to Post-processing](PostProcessing.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to Frequently Asked Questions →](FAQ.md) diff --git a/doc/QuickStart.md b/doc/QuickStart.md index 2b886d2eab..7350245759 100644 --- a/doc/QuickStart.md +++ b/doc/QuickStart.md @@ -22,19 +22,19 @@ This page will give you a basic idea of the QC and how to run it. Please read it *in its entirety* and run the commands along the way. Do not start developing your module before you have reached the next section called "Modules Development". Also, make sure you have pulled the latest QC version. -We would be very grateful if you could report to us any error or inaccuracy you found. +We would be very grateful if you could report to us any error or inaccuracy you find. Thanks! ## Requirements -A CS8 machine (Mac, and in particular Ubuntu, are only supported on a best effort basis, some packages might not build properly). +A RHEL9 machine or a Mac (although you might have some issues running Readout). Ubuntu is only supported on a best-effort basis. ## Setup -1. Setup O2 environment and tools
We use alibuild, **follow the complete instructions** [here](https://alice-doc.github.io/alice-analysis-tutorial/building/). In particular make sure to f: - 1. Install GLFW to have GUIs in the DPL (optional, **DPL GUIs do not work in containers nor over SSH**). - * CS8: `sudo yum install -y glfw-devel --enablerepo=epel` +1. Setup O2 environment and tools
We use aliBuild, **follow the complete instructions** [here](https://alice-doc.github.io/alice-analysis-tutorial/building/). Make sure to: + 1. Install GLFW to have GUIs in the DPL (optional, **the DPL GUI do not work in containers nor over SSH**). + * RHEL: should come with the standard installation * Mac: `brew install glfw` 2. Prepare the QualityControl development package @@ -48,56 +48,69 @@ Note: you can also use the alibuild "defaults" called `o2-dataflow` to avoid bui ### Environment loading -Whenever you want to work with O2 and QualityControl, do `alienv enter QualityControl/latest`. To load multiple independent packages, list them one after the other, space-separated (e.g. `alienv enter QualityControl/latest Readout/latest`). O2 is automatically pulled by QualityControl, thus no need to specify it explicitely. +Whenever you want to work with O2 and QualityControl, do +```alienv enter QualityControl/latest``` + +To load multiple independent packages, list them one after the other, space-separated (e.g. `alienv enter QualityControl/latest Readout/latest`). O2 is automatically pulled by QualityControl, thus no need to specify it explicitly. ## Execution -To make sure that your system is correctly setup, we are going to run a basic QC workflow attached to a simple data producer. We will use central services for the repository and the GUI. If you want to set them up on your computer or in your lab, please have a look [here](Advanced.md#local-ccdb-setup) and [here](Advanced.md#local-qcg-qc-gui-setup). +To make sure that your system is correctly setup, we are going to run a basic QC workflow attached to a simple data producer. + +The QC repository and the GUI are central services, thus they don't have to be installed on your machine. If you want to set them up on your computer or in your lab, please have a look [here](Advanced.md#local-ccdb-setup) and [here](Advanced.md#local-qcg-qc-gui-setup). ### Basic workflow -We are going to run a basic workflow whose various processes are shown in the following schema. +This is the workflow we want to run: ![basic-schema](images/basic-schema.png) -The _Producer_ is a random data generator. In a more realistic setup it would be a processing device or the _Readout_. The _Data Sampling_ is the system in charge of dispatching data samples from the main data flow to the _QC tasks_. It can be configured to dispatch different proportion or different types of data. The __tasks__ are in charge of analyzing the data and preparing QC objects, often histograms, that are then pushed forward every cycle. A cycle is 10 second in this example. In production it is closer to 1 minute. The _Checker_ is in charge of evaluating the _MonitorObjects_ produced by the _QC tasks_. It runs _Checks_ defined by the users, for example checking that the mean is above a certain limit. It can also modify the aspect of the histogram, e.g. by changing the background color or adding a PaveText. Finally the _Checker_ is also in charge of storing the resulting _MonitorObject_ into the repository where it will be accessible by the web GUI. It also pushes it to a _Printer_ for the sake of this tutorial. +- The _Producer_ is a random data generator. In a more realistic setup it would be a processing device or the _Readout_. +- The _Data Sampling_ is the system in charge of dispatching data samples from the main data flow to the _QC tasks_. It can be configured to dispatch different proportion or different types of data. +- The __QC tasks__ are in charge of analyzing the data and preparing QC objects, often histograms, that are then pushed forward every cycle. A cycle is 10 second in this example. In production it is at least 1 minute. +- The _Checker_ is in charge of evaluating the _MonitorObjects_ produced by the _QC tasks_. It runs _Checks_ defined by the users, for example checking that the mean is above a certain limit. It can also modify the aspect of the histogram, e.g. by changing the background color or adding a PaveText. +- Finally the _Checker_ is also in charge of storing the resulting _MonitorObject_ into the repository where it will be accessible by the web GUI. +- It also pushes it to a _Printer_ for the sake of this tutorial. To run it simply do: o2-qc-run-basic -Thanks to the Data Processing Layer (DPL, more details later) it is a single process that steers all the _devices_, i.e. processes making up the workflow. A window should appear that shows a graphical representation of the workflow if you are running locally. If you are running remotely via ssh, the DPL Debug GUI will not open. In some cases, it then requires to run with `-b`. The output of any of the processes is available by double clicking a box. If a box is red it means that the process has stopped, probably abnormally. +Thanks to the _Data Processing Layer_ (DPL, more details later) it is a single process that steers all the processes in the workflow. Processes are called _devices_. + +When running on your computer, locally, a window should appear that shows a graphical representation of the workflow. If you are running remotely via ssh, the DPL Debug GUI will not open. In some cases, it then requires to run with `-b`. + +The output of any of the processes is available by double clicking a box. If a box is red it means that the process has stopped, probably abnormally. This is not the GUI we will use to see the histograms. ![basic-dpl-gui](images/basic-dpl-gui.png) -The example above consists of one DPL workflow which has both the main processing and the QC infrastructure declared inside. In the real case, we would usually prefer to attach the QC without modifying the original topology. It can be done by merging two (or more) workflows, as shown below: +The example above consists of a single DPL workflow containing both the main processing and the QC infrastructure. In a more real case, we would prefer attaching the QC without modifying the original topology. It can be done by merging two (or more) workflows with a pipe (`|`), as shown below: o2-qc-run-producer | o2-qc --config json://${QUALITYCONTROL_ROOT}/etc/basic.json ![basic-schema-2-exe](images/basic-schema-2-exe.png) -This command uses two executables. The first one contains only the _Producer (see Figure above), which represents the data flow to which we want to apply the QC. The second executable generates the QC infrastructure based on the given configuration file (more details in a few sections). These two workflows are joined together using the pipe `|` character. This example illustrates how to add QC to any DPL workflow by using `o2-qc` and passing it a configuration file. +This command uses two executables. The first one contains only the _Producer_ (see Figure above), which represents the data flow to which we want to apply the QC. The second executable generates the QC workflow based on the given configuration file (more details in a few sections). These two workflows are joined together using the pipe `|` character. -__Repository and GUI__ +This example illustrates how to add QC to any DPL workflow by using `o2-qc` and passing it a configuration file. -The data is stored in the [ccdb-test](ccdb-test.cern.ch:8080/browse) at CERN. If everything works fine you should see the objects being published in the QC web GUI (QCG) at this address: [https://qcg-test.cern.ch/?page=objectTree](https://qcg-test.cern.ch/?page=objectTree). The link brings you to the hierarchy of objects (see screenshot below). Open "qc/TST/MO/QcTask" (the task you are running) and click on "example" which is the name of your histogram. +#### Repository and GUI - +The plots are stored in the [ccdb-test](ccdb-test.cern.ch:8080/browse) at CERN. If everything works fine you should see the objects being published in the QC web GUI (QCG) at this address: [https://qcg-test.cern.ch/?page=objectTree](https://qcg-test.cern.ch/?page=objectTree). The link brings you to the hierarchy of objects (see screenshot below). Open "qc/TST/MO/QcTask" (the task you are running) and click on "example" which is the name of your histogram. -![alt text](images/basic-qcg1.png) - +If you click on the item in the tree again, you will see an updated version of the plot. - +![alt text](images/basic-qcg1.png) -__Configuration file__ +#### Configuration file In the example above, the devices are configured in the config file named `basic.json`. It is installed in `$QUALITYCONTROL_ROOT/etc`. Each time you rebuild the code, `$QUALITYCONTROL_ROOT/etc/basic.json` is overwritten by the file in the source directory (`~/alice/QualityControl/Framework/basic.json`). -The configuration for the QC is made of many parameters described in an [advanced section of the documentation](Advanced.md#configuration-files-details). For now we can just see below the definition of a task. `moduleName` and `className` specify respectively the library and the class to load and instantiate to do the actual job of the task. +The configuration for the QC is made of many parameters described in an [advanced section of the documentation](Advanced.md#configuration-files-details). TODO update link + +For now we can see below the definition of a task. `moduleName` and `className` specify respectively the library and the class to load and instantiate to do the actual job of the task. ```json (...) "tasks": { @@ -110,69 +123,6 @@ The configuration for the QC is made of many parameters described in an [advance ``` Try and change the name of the task by replacing `QcTask` by a name of your choice (there are 2 places to update in the config file!). Relaunch the workflows. You should now see the object published under a different directory in the QCG. -### Readout chain (optional) - -In this second example, we are going to use the Readout as our data source. -This example assumes that Readout has been compiled beforehand (`aliBuild build Readout --defaults o2`). - -![alt text](images/readout-schema.png) - -This workflow is a bit different from the basic one. The _Readout_ is not a DPL, nor a FairMQ, device and thus we have to have a _proxy_ to get data from it. This is the extra box going to the _Data Sampling_, which then injects data to the task. This is handled in the _Readout_ as long as you enable the corresponding configuration flag. - -The first thing is to load the environment for the readout in a new terminal: `alienv enter Readout/latest`. - -Then enable the data sampling channel in readout by opening the readout config file located at `$READOUT_ROOT/etc/readout-qc.cfg` and make sure that the following properties are correct: - -``` -# Enable the data sampling -[consumer-fmq-qc] -consumerType=FairMQChannel -enableRawFormat=1 -fmq-name=readout-qc -fmq-address=ipc:///tmp/readout-pipe-1 -fmq-type=pub -fmq-transport=zeromq -unmanagedMemorySize=2G -memoryPoolNumberOfPages=500 -memoryPoolPageSize=1M -enabled=1 -(...) -``` - -Start Readout in a terminal: -``` -o2-readout-exe file://$READOUT_ROOT/etc/readout-qc.cfg -``` - -Start in another terminal the proxy, DataSampling and QC workflows: -``` -o2-qc-run-readout | o2-qc --config json://${QUALITYCONTROL_ROOT}/etc/readout.json -``` - -The data sampling is configured to sample 1% of the data as the readout should run by default at full speed. - -#### Getting real data from readout - -See [these instructions for readout](ModulesDevelopment.md#readout) and [these for O2 utilities](ModulesDevelopment.md#dpl-workflow). - -#### Readout data format as received by the Task - -The header is an O2 header populated with data from the header built by the Readout. -The payload received is a 2MB (configurable) data page made of CRU pages (8kB). - -__Configuration file__ - -The configuration file is installed in `$QUALITYCONTROL_ROOT/etc`. Each time you rebuild the code, `$QUALITYCONTROL_ROOT/etc/readout.json` is overwritten by the file in the source directory (`~/alice/QualityControl/Framework/readout.json`). -To avoid this behaviour and preserve the changes you do to the configuration, you can copy the file and specify the path to it with the parameter `--config` when launch `o2-qc`. - -To change the fraction of the data being monitored, change the option `fraction`. - -``` -"fraction": "0.01", -``` - ---- - ### Post-processing example Now we will run an additional application performing further processing of data generated by the basic workflow. Run it again in one terminal window: @@ -191,5 +141,6 @@ On the [QCG website](https://qcg-test.cern.ch/?page=objectTree) you will see a T The post-processing framework and its convenience classes allow to trend and correlate various characteristics of histograms and other data structures generated by QC tasks and checks. One can create their own post-processing tasks or use the ones included in the framework and configure them for one's own needs. +TODO add a link to the postprocessing chapter [↑ Go to the Table of Content ↑](../README.md) | [Continue to Modules Development →](ModulesDevelopment.md) From 658f6dd0fd0e21b7133520eb9072c46f6d89e8b2 Mon Sep 17 00:00:00 2001 From: Barthelemy Date: Wed, 7 May 2025 15:46:11 +0200 Subject: [PATCH 02/10] [QC-1286] Move the Configuration to a separate file --- README.md | 8 + doc/Advanced.md | 358 +---------------------------------------- doc/Configuration.md | 374 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 383 insertions(+), 357 deletions(-) create mode 100644 doc/Configuration.md diff --git a/README.md b/README.md index 5e4dc6faa1..73a03486ae 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,14 @@ For a general overview of our (O2) software, organization and processes, please * [QC Aggregators configuration](doc/Advanced.md#qc-aggregators-configuration) * [QC Post-processing configuration](doc/Advanced.md#qc-post-processing-configuration) * [External tasks configuration](doc/Advanced.md#external-tasks-configuration) + * [Configuration](doc/Configuration.md) + * [Global configuration structure](doc/Configuration.md#global-configuration-structure) + * [Common configuration](doc/Configuration.md#common-configuration) + * [QC Tasks configuration](doc/Configuration.md#qc-tasks-configuration) + * [QC Checks configuration](doc/Configuration.md#qc-checks-configuration) + * [QC Aggregators configuration](doc/Configuration.md#qc-aggregators-configuration) + * [QC Post-processing configuration](doc/Configuration.md#qc-post-processing-configuration) + * [External tasks configuration](doc/Configuration.md#external-tasks-configuration) * [Miscellaneous](doc/Advanced.md#miscellaneous) * [Data Sampling monitoring](doc/Advanced.md#data-sampling-monitoring) * [Monitoring metrics](doc/Advanced.md#monitoring-metrics) diff --git a/doc/Advanced.md b/doc/Advanced.md index c855713526..52026f8f99 100644 --- a/doc/Advanced.md +++ b/doc/Advanced.md @@ -1563,362 +1563,6 @@ The plot is beautified by the addition of a `TPaveText` containing the quality a The postprocessing task ReferenceComparatorTask draws a given set of plots in comparison with their corresponding references, both as superimposed histograms and as current/reference ratio histograms. See the details [here](https://github.com/AliceO2Group/QualityControl/blob/master/doc/PostProcessing.md#the-referencecomparatortask-class). -## Configuration files details - -The QC requires a number of configuration items. An example config file is -provided in the repo under the name _example-default.json_. This is a quick reference for all the parameters. - -### Global configuration structure - -This is the global structure of the configuration in QC. - -```json -{ - "qc": { - "config": { - - }, - "tasks": { - - }, - "externalTasks": { - - }, - "checks": { - - }, - "aggregators": { - - }, - "postprocessing": { - - } - }, - "dataSamplingPolicies": [ - - ] -} -``` - -There are six QC-related components: -* "config" - contains global configuration of QC which apply to any component. It is required in any configuration - file. -* "tasks" - contains declarations of QC Tasks. It is mandatory for running topologies with Tasks and - Checks. -* "externalTasks" - contains declarations of external devices which sends objects to the QC to be checked and stored. -* "checks" - contains declarations of QC Checks. It is mandatory for running topologies with - Tasks and Checks. -* "aggregators" - contains declarations of QC Aggregators. It is not mandatory. -* "postprocessing" - contains declarations of PostProcessing Tasks. It is only needed only when Post-Processing is - run. - -The configuration file can also include a list of Data Sampling Policies. -Please refer to the [Data Sampling documentation](https://github.com/AliceO2Group/AliceO2/tree/dev/Utilities/DataSampling) to find more information. - -### Common configuration - -This is how a typical "config" structure looks like. Each configuration element is described with a relevant comment -afterwards. The `"": "",` formatting is to keep the JSON structure valid. Please note that these comments -should not be present in real configuration files. - -```json -{ - "qc": { - "config": { - "database": { "": "Configuration of a QC database (the place where QC results are stored).", - "username": "qc_user", "": "Username to log into a DB. Relevant only to the MySQL implementation.", - "password": "qc_user", "": "Password to log into a DB. Relevant only to the MySQL implementation.", - "name": "quality_control", "": "Name of a DB. Relevant only to the MySQL implementation.", - "implementation": "CCDB", "": "Implementation of a DB. It can be CCDB, or MySQL (deprecated).", - "host": "ccdb-test.cern.ch:8080", "": "URL of a DB.", - "maxObjectSize": "2097152", "": "[Bytes, default=2MB] Maximum size allowed, larger objects are rejected." - }, - "Activity": { "": ["Configuration of a QC Activity (Run). This structure is subject to", - "change or the values might come from other source (e.g. ECS+Bookkeeping)." ], - "number": "42", "": "Activity number.", - "type": "PHYSICS", "": "Activity type.", - "periodName": "", "": "Period name - e.g. LHC22c, LHC22c1b_test", - "passName": "", "": "Pass type - e.g. spass, cpass1", - "provenance": "qc", "": "Provenance - qc or qc_mc depending whether it is normal data or monte carlo data", - "start" : "0", "": "Activity start time in ms since epoch. One can use it as a filter in post-processing", - "end" : "1234", "": "Activity end time in ms since epoch. One can use it as a filter in post-processing", - "beamType" : "pp", "": "Beam type: `pp`, `PbPb`, `pPb` ", - "partitionName" : "", "": "Partition name", - "fillNumber" : "123", "": "Fill Number" - }, - "monitoring": { "": "Configuration of the Monitoring library.", - "url": "infologger:///debug?qc", "": ["URI to the Monitoring backend. Refer to the link below for more info:", - "https://github.com/AliceO2Group/Monitoring#monitoring-instance"] - }, - "consul": { "": "Configuration of the Consul library (used for Service Discovery).", - "url": "", "": "URL of the Consul backend" - }, - "conditionDB": { "": ["Configuration of the Conditions and Calibration DataBase (CCDB).", - "Do not mistake with the CCDB which is used as QC repository."], - "url": "ccdb-test.cern.ch:8080", "": "URL of a CCDB" - }, - "infologger": { "": "Configuration of the Infologger (optional).", - "filterDiscardDebug": "false", "": "Set to 1 to discard debug and trace messages (default: false)", - "filterDiscardLevel": "2", "": "Message at this level or above are discarded (default: 21 - Trace)", - "filterDiscardFile": "", "": ["If set, the discarded messages will go to this file (default: )", - "The keyword _ID_, if used, is replaced by the device ID."], - "filterRotateMaxBytes": "", "": "Maximum size of the discard file.", - "filterRotateMaxFiles": "", "": "Maximum number of discard files.", - "debugInDiscardFile": "false", "": "If true, the debug discarded messages go to the file (default: false)." - }, - "bookkeeping": { "": "Configuration of the bookkeeping (optional)", - "url": "localhost:4001", "": "Url of the bookkeeping API (port is usually different from web interface)" - }, - "kafka": { - "url": "kafka-broker:123", "": "url of the kafka broker", - "topicAliecsRun":"aliecs.run", "": "the topic where AliECS publishes Run Events, 'aliecs.run' by default" - }, - "postprocessing": { "": "Configuration parameters for post-processing", - "periodSeconds": 10.0, "": "Sets the interval of checking all the triggers. One can put a very small value", - "": "for async processing, but use 10 or more seconds for synchronous operations", - "matchAnyRunNumber": "false", "": "Forces post-processing triggers to match any run, useful when running with AliECS" - } - } - } -} -``` - -#### Common configuration in production - -In production at P2 and in staging, some common items are defined globally in the file `QC/general-config-params`: - -* QCDB -* monitoring -* consul -* conditionDB -* bookkeeping -* - -It is mandatory to use them by including the file: - -``` -"config": { - {% include "QC/general-config-params" %} - }, -``` - -Other configuration items can still be added in your files as such (note the comma after the inclusion) : - -``` - "config": { - {% include "QC/general-config-params" %}, - "infologger": { - "filterDiscardDebug": "false", - "filterDiscardLevel": "22" - }, - "postprocessing": { - "matchAnyRunNumber": "true" - } - }, -``` - -Please, do not use `Activity` in production ! - -### QC Tasks configuration - -Below the full QC Task configuration structure is described. Note that more than one task might be declared inside in -the "tasks" path. - - ```json -{ - "qc": { - "tasks": { - "QcTaskID": { "": ["ID of the QC Task. Less than 14 character names are preferred.", - "If \"taskName\" is empty or missing, the ID is used"], - "active": "true", "": "Activation flag. If not \"true\", the Task will not be created.", - "taskName": "MyTaskName", "": ["Name of the task, used e.g. in the QCDB. If empty, the ID is used.", - "Less than 14 character names are preferred."], - "className": "namespace::of::Task", "": "Class name of the QC Task with full namespace.", - "moduleName": "QcSkeleton", "": "Library name. It can be found in CMakeLists of the detector module.", - "detectorName": "TST", "": "3-letter code of the detector.", - "critical": "true", "": "if false the task is allowed to die without stopping the workflow, default: true", - "cycleDurationSeconds": "10", "": "Cycle duration (how often objects are published), 10 seconds minimum.", - "": "The first cycle will be randomly shorter. ", - "": "Alternatively, one can specify different cycle durations for different periods. The last item in cycleDurations will be used for the rest of the duration whatever the period. The first cycle will be randomly shorter.", - "cycleDurations": [ - {"cycleDurationSeconds": 10, "validitySeconds": 35}, - {"cycleDurationSeconds": 12, "validitySeconds": 1} - ], - "maxNumberCycles": "-1", "": "Number of cycles to perform. Use -1 for infinite.", - "disableLastCycle": "true", "": "Last cycle, upon EndOfStream, is not published. (default: false)", - "dataSources": [{ "": "Data sources of the QC Task. The following are supported", - "type": "dataSamplingPolicy", "": "Type of the data source", - "name": "tst-raw", "": "Name of Data Sampling Policy" - }, { - "type": "direct", "": "connects directly to another output", - "query": "raw:TST/RAWDATA/0", "": "input spec query, as expected by DataDescriptorQueryBuilder" - }], - "taskParameters": { "": "User Task parameters which are then accessible as a key-value map.", - "myOwnKey": "myOwnValue", "": "An example of a key and a value. Nested structures are not supported" - }, - "resetAfterCycles" : "0", "": "Makes the Task or Merger reset MOs each n cycles.", - "": "0 (default) means that MOs should cover the full run.", - "location": "local", "": ["Location of the QC Task, it can be local or remote. Needed only for", - "multi-node setups, not respected in standalone development setups."], - "localMachines": [ "", "List of local machines where the QC task should run. Required only", - "", "for multi-node setups. An alias can be used if merging deltas.", - "o2flp1", "", "Hostname of a local machine.", - "o2flp2", "", "Hostname of a local machine." - ], - "remoteMachine": "o2qc1", "": "Remote QC machine hostname. Required only for multi-node setups with EPNs.", - "remotePort": "30432", "": "Remote QC machine TCP port. Required only for multi-node setups with EPNs.", - "localControl": "aliecs", "": ["Control software specification, \"aliecs\" (default) or \"odc\").", - "Needed only for multi-node setups."], - "mergingMode": "delta", "": "Merging mode, \"delta\" (default) or \"entire\" objects are expected", - "mergerCycleMultiplier": "1", "": "Multiplies the Merger cycle duration with respect to the QC Task cycle" - "mergersPerLayer": [ "3", "1" ], "": "Defines the number of Mergers per layer, the default is [\"1\"]", - "grpGeomRequest" : { "": "Requests to retrieve GRP objects, then available in GRPGeomHelper::instance()", - "geomRequest": "None", "": "Available options are \"None\", \"Aligned\", \"Ideal\", \"Alignements\"", - "askGRPECS": "false", - "askGRPLHCIF": "false", - "askGRPMagField": "false", - "askMatLUT": "false", - "askTime": "false", - "askOnceAllButField": "false", - "needPropagatorD": "false" - }, - "globalTrackingDataRequest": { "": "A helper to add tracks or clusters to inputs of the task", - "canProcessTracks" : "ITS,ITS-TPC", "": "tracks that the QC task can process, usually should not change", - "requestTracks" : "ITS,TPC-TRD", "": ["tracks that the QC task should process, TPC-TRD will not be", - "requested, as it is not listed in the previous parameter"], - "canProcessClusters" : "TPC", "": "clusters that the QC task can process", - "requestClusters" : "TPC", "": "clusters that the QC task should process", - "mc" : "false", "": "mc boolean flag for the data request" - } - } - } - } -} -``` - -### QC Checks configuration - -Below the full QC Checks configuration structure is described. Note that more than one check might be declared inside in -the "checks" path. Please also refer to [the Checks documentation](ModulesDevelopment.md#configuration) for more details. - - ```json -{ - "qc": { - "checks": { - "MeanIsAbove": { "": ["ID of the Check. Less than 12 character names are preferred.", - "If \"checkName\" is empty or missing, the ID is used"], - "active": "true", "": "Activation flag. If not \"true\", the Check will not be run.", - "checkName": "MeanIsAbove", "": ["Name of the check, used e.g. in the QCDB. If empty, the ID is used.", - "Less than 12 character names are preferred."], - "className": "ns::of::Check", "": "Class name of the QC Check with full namespace.", - "moduleName": "QcCommon", "": "Library name. It can be found in CMakeLists of the detector module.", - "detectorName": "TST", "": "3-letter code of the detector.", - "policy": "OnAny", "": ["Policy which determines when MOs should be checked. See the documentation", - "of Checks for the list of available policies and their behaviour."], - "exportToBookkeeping": "true","": "Flag that toggles reporting of QcFlags created by this check into BKP via gRPC.", - "When not presented it equals to "false"" - "dataSource": [{ "": "List of data source of the Check.", - "type": "Task", "": "Type of the data source, \"Task\", \"ExternalTask\" or \"PostProcessing\"", - "name": "myTask_1", "": "Name of the Task", - "MOs": [ "example" ], "": ["List of MOs to be checked. " - "Can be omitted to mean \"all\"."] - }], - "checkParameters": { "": "User Check parameters which are then accessible as a key-value map.", - "myOwnKey": "myOwnValue", "": "An example of a key and a value. Nested structures are not supported" - } - } - } - } -} -``` - -### QC Aggregators configuration - -Below the full QC Aggregators configuration structure is described. Note that more than one aggregator might be declared inside in -the "aggregators" path. Please also refer to [the Aggregators documentation](ModulesDevelopment.md#quality-aggregation) for more details. - -```json -{ - "qc": { - "aggregators": { - "MyAggregator1": { "": "ID of the Aggregator. Less than 12 character names are preferred.", - "active": "true", "": "Activation flag. If not \"true\", the Aggregator will not be run.", - "aggregatorName" : "MyAggregator1", "": ["Name of the Aggregator, used e.g. in the QCDB. If empty, the ID is used.", - "Less than 12 character names are preferred."], - "className": "ns::of::Aggregator", "": "Class name of the QC Aggregator with full namespace.", - "moduleName": "QcCommon", "": "Library name. It can be found in CMakeLists of the detector module.", - "policy": "OnAny", "": ["Policy which determines when QOs should be aggregated. See the documentation", - "of Aggregators for the list of available policies and their behaviour."], - "exportToBookkeeping": "true", "": "Flag that toggles reporting of QcFlags created by all of the sources into - "the BKP via gRPC. "When not presented it equals to "false"" - "detectorName": "TST", "": "3-letter code of the detector.", - "dataSource": [{ "": "List of data source of the Aggregator.", - "type": "Check",, "": "Type of the data source: \"Check\" or \"Aggregator\"", - "name": "dataSizeCheck", "": "Name of the Check or Aggregator", - "QOs": ["newQuality", "another"], "": ["List of QOs to be checked.", - "Can be omitted for Checks", - "that publish a single Quality or to mean \"all\"."] - }] - } - } - } -} -``` - -### QC Post-processing configuration - -Below the full QC Post-processing (PP) configuration structure is described. Note that more than one PP Task might be -declared inside in the "postprocessing" path. Please also refer to [the Post-processing documentation](PostProcessing.md) for more details. - -```json -{ - "qc": { - "postprocessing": { - "ExamplePostprocessingID": { "": "ID of the PP Task.", - "active": "true", "": "Activation flag. If not \"true\", the PP Task will not be run.", - "taskName": "MyPPTaskName", "": ["Name of the task, used e.g. in the QCDB. If empty, the ID is used.", - "Less than 14 character names are preferred."], - "className": "namespace::of::PPTask", "": "Class name of the PP Task with full namespace.", - "moduleName": "QcSkeleton", "": "Library name. It can be found in CMakeLists of the detector module.", - "detectorName": "TST", "": "3-letter code of the detector.", - "initTrigger": [ "", "List of initialization triggers", - "userorcontrol", "", "An example of an init trigger" - ], - "updateTrigger": [ "", "List of update triggers", - "10min", "", "An example of an update trigger" - ], - "stopTrigger": [ "", "List of stop triggers", - "userorcontrol", "", "An example of a stop trigger" - ], - "validityFromLastTriggerOnly": "false", "": "If true, the output objects will use validity of the last trigger,", - "": "otherwise a union of all triggers' validity is used by default.", - "sourceRepo": { "": "It allows to specify a different repository for the input objects.", - "implementation": "CCDB", - "host": "another-test.cern.ch:8080" - } - } - } - } -} -``` - -### External tasks configuration - -Below the external task configuration structure is described. Note that more than one external task might be declared inside in the "externalTasks" path. - -```json -{ - "qc": { - "externalTasks": { - "External-1": { "": "ID of the task", - "active": "true", "": "Activation flag. If not \"true\", the Task will not be created.", - "taskName": "External-1", "": "Name of the task, used e.g. in the QCDB. If empty, the ID is used.", - "query": "External-1:TST/HISTO/0", "": "Query specifying where the objects to be checked and stored are coming from. Use the task name as binding." - } - } - } -} -``` # Miscellaneous @@ -2180,4 +1824,4 @@ To change the fraction of the data being monitored, change the option `fraction` --- -[← Go back to Post-processing](PostProcessing.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to Frequently Asked Questions →](FAQ.md) +[← Go back to Post-processing](PostProcessing.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to Configuration →](Configuration.md) diff --git a/doc/Configuration.md b/doc/Configuration.md new file mode 100644 index 0000000000..52b90cc41d --- /dev/null +++ b/doc/Configuration.md @@ -0,0 +1,374 @@ + +Configuration reference +--- + + + + + * [Global configuration structure](./QualityControl/doc/Configuration.md#global-configuration-structure) + * [Common configuration](./QualityControl/doc/Configuration.md#common-configuration) + * [QC Tasks configuration](./QualityControl/doc/Configuration.md#qc-tasks-configuration) + * [QC Checks configuration](./QualityControl/doc/Configuration.md#qc-checks-configuration) + * [QC Aggregators configuration](./QualityControl/doc/Configuration.md#qc-aggregators-configuration) + * [QC Post-processing configuration](./QualityControl/doc/Configuration.md#qc-post-processing-configuration) + * [External tasks configuration](./QualityControl/doc/Configuration.md#external-tasks-configuration) + + + +The QC requires a number of configuration items. An example config file is +provided in the repo under the name _example-default.json_. This is a quick reference for all the parameters. + +## Global configuration structure + +This is the global structure of the configuration in QC. + +```json +{ + "qc": { + "config": { + + }, + "tasks": { + + }, + "externalTasks": { + + }, + "checks": { + + }, + "aggregators": { + + }, + "postprocessing": { + + } + }, + "dataSamplingPolicies": [ + + ] +} +``` + +There are six QC-related components: +* "config" - contains global configuration of QC which apply to any component. It is required in any configuration + file. +* "tasks" - contains declarations of QC Tasks. It is mandatory for running topologies with Tasks and + Checks. +* "externalTasks" - contains declarations of external devices which sends objects to the QC to be checked and stored. +* "checks" - contains declarations of QC Checks. It is mandatory for running topologies with + Tasks and Checks. +* "aggregators" - contains declarations of QC Aggregators. It is not mandatory. +* "postprocessing" - contains declarations of PostProcessing Tasks. It is only needed only when Post-Processing is + run. + +The configuration file can also include a list of Data Sampling Policies. +Please refer to the [Data Sampling documentation](https://github.com/AliceO2Group/AliceO2/tree/dev/Utilities/DataSampling) to find more information. + +## Common configuration + +This is how a typical "config" structure looks like. Each configuration element is described with a relevant comment +afterwards. The `"": "",` formatting is to keep the JSON structure valid. Please note that these comments +should not be present in real configuration files. + +```json +{ + "qc": { + "config": { + "database": { "": "Configuration of a QC database (the place where QC results are stored).", + "username": "qc_user", "": "Username to log into a DB. Relevant only to the MySQL implementation.", + "password": "qc_user", "": "Password to log into a DB. Relevant only to the MySQL implementation.", + "name": "quality_control", "": "Name of a DB. Relevant only to the MySQL implementation.", + "implementation": "CCDB", "": "Implementation of a DB. It can be CCDB, or MySQL (deprecated).", + "host": "ccdb-test.cern.ch:8080", "": "URL of a DB.", + "maxObjectSize": "2097152", "": "[Bytes, default=2MB] Maximum size allowed, larger objects are rejected." + }, + "Activity": { "": ["Configuration of a QC Activity (Run). This structure is subject to", + "change or the values might come from other source (e.g. ECS+Bookkeeping)." ], + "number": "42", "": "Activity number.", + "type": "PHYSICS", "": "Activity type.", + "periodName": "", "": "Period name - e.g. LHC22c, LHC22c1b_test", + "passName": "", "": "Pass type - e.g. spass, cpass1", + "provenance": "qc", "": "Provenance - qc or qc_mc depending whether it is normal data or monte carlo data", + "start" : "0", "": "Activity start time in ms since epoch. One can use it as a filter in post-processing", + "end" : "1234", "": "Activity end time in ms since epoch. One can use it as a filter in post-processing", + "beamType" : "pp", "": "Beam type: `pp`, `PbPb`, `pPb` ", + "partitionName" : "", "": "Partition name", + "fillNumber" : "123", "": "Fill Number" + }, + "monitoring": { "": "Configuration of the Monitoring library.", + "url": "infologger:///debug?qc", "": ["URI to the Monitoring backend. Refer to the link below for more info:", + "https://github.com/AliceO2Group/Monitoring#monitoring-instance"] + }, + "consul": { "": "Configuration of the Consul library (used for Service Discovery).", + "url": "", "": "URL of the Consul backend" + }, + "conditionDB": { "": ["Configuration of the Conditions and Calibration DataBase (CCDB).", + "Do not mistake with the CCDB which is used as QC repository."], + "url": "ccdb-test.cern.ch:8080", "": "URL of a CCDB" + }, + "infologger": { "": "Configuration of the Infologger (optional).", + "filterDiscardDebug": "false", "": "Set to 1 to discard debug and trace messages (default: false)", + "filterDiscardLevel": "2", "": "Message at this level or above are discarded (default: 21 - Trace)", + "filterDiscardFile": "", "": ["If set, the discarded messages will go to this file (default: )", + "The keyword _ID_, if used, is replaced by the device ID."], + "filterRotateMaxBytes": "", "": "Maximum size of the discard file.", + "filterRotateMaxFiles": "", "": "Maximum number of discard files.", + "debugInDiscardFile": "false", "": "If true, the debug discarded messages go to the file (default: false)." + }, + "bookkeeping": { "": "Configuration of the bookkeeping (optional)", + "url": "localhost:4001", "": "Url of the bookkeeping API (port is usually different from web interface)" + }, + "kafka": { + "url": "kafka-broker:123", "": "url of the kafka broker", + "topicAliecsRun":"aliecs.run", "": "the topic where AliECS publishes Run Events, 'aliecs.run' by default" + }, + "postprocessing": { "": "Configuration parameters for post-processing", + "periodSeconds": 10.0, "": "Sets the interval of checking all the triggers. One can put a very small value", + "": "for async processing, but use 10 or more seconds for synchronous operations", + "matchAnyRunNumber": "false", "": "Forces post-processing triggers to match any run, useful when running with AliECS" + } + } + } +} +``` + +### Common configuration in production + +In production at P2 and in staging, some common items are defined globally in the file `QC/general-config-params`: + +* QCDB +* monitoring +* consul +* conditionDB +* bookkeeping +* + +It is mandatory to use them by including the file: + +``` +"config": { + {% include "QC/general-config-params" %} + }, +``` + +Other configuration items can still be added in your files as such (note the comma after the inclusion) : + +``` + "config": { + {% include "QC/general-config-params" %}, + "infologger": { + "filterDiscardDebug": "false", + "filterDiscardLevel": "22" + }, + "postprocessing": { + "matchAnyRunNumber": "true" + } + }, +``` + +Please, do not use `Activity` in production ! + +## QC Tasks configuration + +Below the full QC Task configuration structure is described. Note that more than one task might be declared inside in +the "tasks" path. + + ```json +{ + "qc": { + "tasks": { + "QcTaskID": { "": ["ID of the QC Task. Less than 14 character names are preferred.", + "If \"taskName\" is empty or missing, the ID is used"], + "active": "true", "": "Activation flag. If not \"true\", the Task will not be created.", + "taskName": "MyTaskName", "": ["Name of the task, used e.g. in the QCDB. If empty, the ID is used.", + "Less than 14 character names are preferred."], + "className": "namespace::of::Task", "": "Class name of the QC Task with full namespace.", + "moduleName": "QcSkeleton", "": "Library name. It can be found in CMakeLists of the detector module.", + "detectorName": "TST", "": "3-letter code of the detector.", + "critical": "true", "": "if false the task is allowed to die without stopping the workflow, default: true", + "cycleDurationSeconds": "10", "": "Cycle duration (how often objects are published), 10 seconds minimum.", + "": "The first cycle will be randomly shorter. ", + "": "Alternatively, one can specify different cycle durations for different periods. The last item in cycleDurations will be used for the rest of the duration whatever the period. The first cycle will be randomly shorter.", + "cycleDurations": [ + {"cycleDurationSeconds": 10, "validitySeconds": 35}, + {"cycleDurationSeconds": 12, "validitySeconds": 1} + ], + "maxNumberCycles": "-1", "": "Number of cycles to perform. Use -1 for infinite.", + "disableLastCycle": "true", "": "Last cycle, upon EndOfStream, is not published. (default: false)", + "dataSources": [{ "": "Data sources of the QC Task. The following are supported", + "type": "dataSamplingPolicy", "": "Type of the data source", + "name": "tst-raw", "": "Name of Data Sampling Policy" + }, { + "type": "direct", "": "connects directly to another output", + "query": "raw:TST/RAWDATA/0", "": "input spec query, as expected by DataDescriptorQueryBuilder" + }], + "taskParameters": { "": "User Task parameters which are then accessible as a key-value map.", + "myOwnKey": "myOwnValue", "": "An example of a key and a value. Nested structures are not supported" + }, + "resetAfterCycles" : "0", "": "Makes the Task or Merger reset MOs each n cycles.", + "": "0 (default) means that MOs should cover the full run.", + "location": "local", "": ["Location of the QC Task, it can be local or remote. Needed only for", + "multi-node setups, not respected in standalone development setups."], + "localMachines": [ "", "List of local machines where the QC task should run. Required only", + "", "for multi-node setups. An alias can be used if merging deltas.", + "o2flp1", "", "Hostname of a local machine.", + "o2flp2", "", "Hostname of a local machine." + ], + "remoteMachine": "o2qc1", "": "Remote QC machine hostname. Required only for multi-node setups with EPNs.", + "remotePort": "30432", "": "Remote QC machine TCP port. Required only for multi-node setups with EPNs.", + "localControl": "aliecs", "": ["Control software specification, \"aliecs\" (default) or \"odc\").", + "Needed only for multi-node setups."], + "mergingMode": "delta", "": "Merging mode, \"delta\" (default) or \"entire\" objects are expected", + "mergerCycleMultiplier": "1", "": "Multiplies the Merger cycle duration with respect to the QC Task cycle" + "mergersPerLayer": [ "3", "1" ], "": "Defines the number of Mergers per layer, the default is [\"1\"]", + "grpGeomRequest" : { "": "Requests to retrieve GRP objects, then available in GRPGeomHelper::instance()", + "geomRequest": "None", "": "Available options are \"None\", \"Aligned\", \"Ideal\", \"Alignements\"", + "askGRPECS": "false", + "askGRPLHCIF": "false", + "askGRPMagField": "false", + "askMatLUT": "false", + "askTime": "false", + "askOnceAllButField": "false", + "needPropagatorD": "false" + }, + "globalTrackingDataRequest": { "": "A helper to add tracks or clusters to inputs of the task", + "canProcessTracks" : "ITS,ITS-TPC", "": "tracks that the QC task can process, usually should not change", + "requestTracks" : "ITS,TPC-TRD", "": ["tracks that the QC task should process, TPC-TRD will not be", + "requested, as it is not listed in the previous parameter"], + "canProcessClusters" : "TPC", "": "clusters that the QC task can process", + "requestClusters" : "TPC", "": "clusters that the QC task should process", + "mc" : "false", "": "mc boolean flag for the data request" + } + } + } + } +} +``` + +## QC Checks configuration + +Below the full QC Checks configuration structure is described. Note that more than one check might be declared inside in +the "checks" path. Please also refer to [the Checks documentation](ModulesDevelopment.md#configuration) for more details. + + ```json +{ + "qc": { + "checks": { + "MeanIsAbove": { "": ["ID of the Check. Less than 12 character names are preferred.", + "If \"checkName\" is empty or missing, the ID is used"], + "active": "true", "": "Activation flag. If not \"true\", the Check will not be run.", + "checkName": "MeanIsAbove", "": ["Name of the check, used e.g. in the QCDB. If empty, the ID is used.", + "Less than 12 character names are preferred."], + "className": "ns::of::Check", "": "Class name of the QC Check with full namespace.", + "moduleName": "QcCommon", "": "Library name. It can be found in CMakeLists of the detector module.", + "detectorName": "TST", "": "3-letter code of the detector.", + "policy": "OnAny", "": ["Policy which determines when MOs should be checked. See the documentation", + "of Checks for the list of available policies and their behaviour."], + "exportToBookkeeping": "true","": "Flag that toggles reporting of QcFlags created by this check into BKP via gRPC.", + "When not presented it equals to "false"" + "dataSource": [{ "": "List of data source of the Check.", + "type": "Task", "": "Type of the data source, \"Task\", \"ExternalTask\" or \"PostProcessing\"", + "name": "myTask_1", "": "Name of the Task", + "MOs": [ "example" ], "": ["List of MOs to be checked. " + "Can be omitted to mean \"all\"."] + }], + "checkParameters": { "": "User Check parameters which are then accessible as a key-value map.", + "myOwnKey": "myOwnValue", "": "An example of a key and a value. Nested structures are not supported" + } + } + } + } +} +``` + +## QC Aggregators configuration + +Below the full QC Aggregators configuration structure is described. Note that more than one aggregator might be declared inside in +the "aggregators" path. Please also refer to [the Aggregators documentation](ModulesDevelopment.md#quality-aggregation) for more details. + +```json +{ + "qc": { + "aggregators": { + "MyAggregator1": { "": "ID of the Aggregator. Less than 12 character names are preferred.", + "active": "true", "": "Activation flag. If not \"true\", the Aggregator will not be run.", + "aggregatorName" : "MyAggregator1", "": ["Name of the Aggregator, used e.g. in the QCDB. If empty, the ID is used.", + "Less than 12 character names are preferred."], + "className": "ns::of::Aggregator", "": "Class name of the QC Aggregator with full namespace.", + "moduleName": "QcCommon", "": "Library name. It can be found in CMakeLists of the detector module.", + "policy": "OnAny", "": ["Policy which determines when QOs should be aggregated. See the documentation", + "of Aggregators for the list of available policies and their behaviour."], + "exportToBookkeeping": "true", "": "Flag that toggles reporting of QcFlags created by all of the sources into + "the BKP via gRPC. "When not presented it equals to "false"" + "detectorName": "TST", "": "3-letter code of the detector.", + "dataSource": [{ "": "List of data source of the Aggregator.", + "type": "Check",, "": "Type of the data source: \"Check\" or \"Aggregator\"", + "name": "dataSizeCheck", "": "Name of the Check or Aggregator", + "QOs": ["newQuality", "another"], "": ["List of QOs to be checked.", + "Can be omitted for Checks", + "that publish a single Quality or to mean \"all\"."] + }] + } + } + } +} +``` + +## QC Post-processing configuration + +Below the full QC Post-processing (PP) configuration structure is described. Note that more than one PP Task might be +declared inside in the "postprocessing" path. Please also refer to [the Post-processing documentation](PostProcessing.md) for more details. + +```json +{ + "qc": { + "postprocessing": { + "ExamplePostprocessingID": { "": "ID of the PP Task.", + "active": "true", "": "Activation flag. If not \"true\", the PP Task will not be run.", + "taskName": "MyPPTaskName", "": ["Name of the task, used e.g. in the QCDB. If empty, the ID is used.", + "Less than 14 character names are preferred."], + "className": "namespace::of::PPTask", "": "Class name of the PP Task with full namespace.", + "moduleName": "QcSkeleton", "": "Library name. It can be found in CMakeLists of the detector module.", + "detectorName": "TST", "": "3-letter code of the detector.", + "initTrigger": [ "", "List of initialization triggers", + "userorcontrol", "", "An example of an init trigger" + ], + "updateTrigger": [ "", "List of update triggers", + "10min", "", "An example of an update trigger" + ], + "stopTrigger": [ "", "List of stop triggers", + "userorcontrol", "", "An example of a stop trigger" + ], + "validityFromLastTriggerOnly": "false", "": "If true, the output objects will use validity of the last trigger,", + "": "otherwise a union of all triggers' validity is used by default.", + "sourceRepo": { "": "It allows to specify a different repository for the input objects.", + "implementation": "CCDB", + "host": "another-test.cern.ch:8080" + } + } + } + } +} +``` + +## External tasks configuration + +Below the external task configuration structure is described. Note that more than one external task might be declared inside in the "externalTasks" path. + +```json +{ + "qc": { + "externalTasks": { + "External-1": { "": "ID of the task", + "active": "true", "": "Activation flag. If not \"true\", the Task will not be created.", + "taskName": "External-1", "": "Name of the task, used e.g. in the QCDB. If empty, the ID is used.", + "query": "External-1:TST/HISTO/0", "": "Query specifying where the objects to be checked and stored are coming from. Use the task name as binding." + } + } + } +} +``` +--- + +[← Go back to Advanced](Advanced.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to Frequently Asked Questions →](FAQ.md) From ba9c12a672ada421178d46ab8cf2ceb6c72977ff Mon Sep 17 00:00:00 2001 From: Barthelemy Date: Wed, 7 May 2025 16:17:48 +0200 Subject: [PATCH 03/10] [QC-1286] Extract more parts --- README.md | 26 +- doc/Advanced.md | 1316 +----------------------------------------- doc/Configuration.md | 363 +++++++++++- doc/FLPsuite.md | 208 +++++++ doc/Framework.md | 733 +++++++++++++++++++++++ 5 files changed, 1336 insertions(+), 1310 deletions(-) create mode 100644 doc/FLPsuite.md create mode 100644 doc/Framework.md diff --git a/README.md b/README.md index 73a03486ae..50d962f093 100644 --- a/README.md +++ b/README.md @@ -108,14 +108,24 @@ For a general overview of our (O2) software, organization and processes, please * [Definition and access of simple user-defined task configuration ("taskParameters")](doc/Advanced.md#definition-and-access-of-simple-user-defined-task-configuration-taskparameters) * [Definition and access of user-defined configuration ("extendedTaskParameters")](doc/Advanced.md#definition-and-access-of-user-defined-configuration-extendedtaskparameters) * [Definition of new arguments](doc/Advanced.md#definition-of-new-arguments) - * [Configuration files details](doc/Advanced.md#configuration-files-details) - * [Global configuration structure](doc/Advanced.md#global-configuration-structure) - * [Common configuration](doc/Advanced.md#common-configuration) - * [QC Tasks configuration](doc/Advanced.md#qc-tasks-configuration) - * [QC Checks configuration](doc/Advanced.md#qc-checks-configuration) - * [QC Aggregators configuration](doc/Advanced.md#qc-aggregators-configuration) - * [QC Post-processing configuration](doc/Advanced.md#qc-post-processing-configuration) - * [External tasks configuration](doc/Advanced.md#external-tasks-configuration) + * [Plugging the QC to an existing DPL workflow](#plugging-the-qc-to-an-existing-dpl-workflow) + * [Production of QC objects outside this framework](#production-of-qc-objects-outside-this-framework) + * [Multi-node setups](#multi-node-setups) + * [Batch processing](#batch-processing) + * [Moving window](#moving-window) + * [Monitor cycles](#monitor-cycles) + * [Writing a DPL data producer](#writing-a-dpl-data-producer) + * [Custom merging](#custom-merging) + * [Critical, resilient and non-critical tasks](#critical-resilient-and-non-critical-tasks) + * [QC with DPL Analysis](#qc-with-dpl-analysis) + * [Uploading objects to QCDB](#uploading-objects-to-qcdb) + * [Propagating Check results to RCT in Bookkeeping](#propagating-check-results-to-rct-in-bookkeeping) + * [Conversion details](#conversion-details) + * [Solving performance issues](#solving-performance-issues) + * [Dispatcher](#dispatcher) + * [QC Tasks](#qc-tasks-1) + * [Mergers](#mergers) + * [Understanding and reducing memory footprint](#understanding-and-reducing-memory-footprint) * [Configuration](doc/Configuration.md) * [Global configuration structure](doc/Configuration.md#global-configuration-structure) * [Common configuration](doc/Configuration.md#common-configuration) diff --git a/doc/Advanced.md b/doc/Advanced.md index 52026f8f99..d501bdffe9 100644 --- a/doc/Advanced.md +++ b/doc/Advanced.md @@ -5,778 +5,36 @@ Advanced topics -* [Advanced topics](#advanced-topics) -* [Framework](#framework) - * [Plugging the QC to an existing DPL workflow](#plugging-the-qc-to-an-existing-dpl-workflow) - * [Production of QC objects outside this framework](#production-of-qc-objects-outside-this-framework) - * [Configuration](#configuration) - * [Example 1: basic](#example-1-basic) - * [Example 2: advanced](#example-2-advanced) - * [Limitations](#limitations) - * [Multi-node setups](#multi-node-setups) - * [Batch processing](#batch-processing) - * [Moving window](#moving-window) - * [Monitor cycles](#monitor-cycles) - * [Writing a DPL data producer](#writing-a-dpl-data-producer) - * [Custom merging](#custom-merging) - * [Critical, resilient and non-critical tasks](#critical-resilient-and-non-critical-tasks) - * [QC with DPL Analysis](#qc-with-dpl-analysis) - * [Uploading objects to QCDB](#uploading-objects-to-qcdb) - * [Propagating Check results to RCT in Bookkeeping](#propagating-check-results-to-rct-in-bookkeeping) - * [Conversion details](#conversion-details) -* [Solving performance issues](#solving-performance-issues) - * [Dispatcher](#dispatcher) - * [QC Tasks](#qc-tasks-1) - * [Mergers](#mergers) -* [Understanding and reducing memory footprint](#understanding-and-reducing-memory-footprint) - * [Analysing memory usage with valgrind](#analysing-memory-usage-with-valgrind) + * [Advanced topics](#advanced-topics) * [CCDB / QCDB](#ccdb--qcdb) - * [Accessing objects in CCDB](#accessing-objects-in-ccdb) - * [Access GRP objects with GRP Geom Helper](#access-grp-objects-with-grp-geom-helper) - * [Global Tracking Data Request helper](#global-tracking-data-request-helper) - * [Custom metadata](#custom-metadata) - * [Details on the data storage format in the CCDB](#details-on-the-data-storage-format-in-the-ccdb) - * [Local CCDB setup](#local-ccdb-setup) - * [Instructions to move an object in the QCDB](#instructions-to-move-an-object-in-the-qcdb) + * [Accessing objects in CCDB](#accessing-objects-in-ccdb) + * [Accessing from a Postprocessing task](#accessing-from-a-postprocessing-task) + * [Access GRP objects with GRP Geom Helper](#access-grp-objects-with-grp-geom-helper) + * [Global Tracking Data Request helper](#global-tracking-data-request-helper) + * [Custom metadata](#custom-metadata) + * [Details on the data storage format in the CCDB](#details-on-the-data-storage-format-in-the-ccdb) + * [Data storage format before v0.14 and ROOT 6.18](#data-storage-format-before-v014-and-root-618) + * [Local CCDB setup](#local-ccdb-setup) + * [Instructions to move an object in the QCDB](#instructions-to-move-an-object-in-the-qcdb) * [Asynchronous Data and Monte Carlo QC operations](#asynchronous-data-and-monte-carlo-qc-operations) * [QCG](#qcg) - * [Display a non-standard ROOT object in QCG](#display-a-non-standard-root-object-in-qcg) - * [Canvas options](#canvas-options) - * [Local QCG (QC GUI) setup](#local-qcg-qc-gui-setup) -* [FLP Suite](#flp-suite) - * [Developing QC modules on a machine with FLP suite](#developing-qc-modules-on-a-machine-with-flp-suite) - * [Switch detector in the workflow readout-dataflow](#switch-detector-in-the-workflow-readout-dataflow) - * [Get all the task output to the infologger](#get-all-the-task-output-to-the-infologger) - * [Using a different config file with the general QC](#using-a-different-config-file-with-the-general-qc) - * [Enable the repo cleaner](#enable-the-repo-cleaner) -* [Configuration](#configuration-1) - * [Merging multiple configuration files into one](#merging-multiple-configuration-files-into-one) - * [Definition and access of simple user-defined task configuration ("taskParameters")](#definition-and-access-of-simple-user-defined-task-configuration-taskparameters) - * [Definition and access of user-defined configuration ("extendedTaskParameters")](#definition-and-access-of-user-defined-configuration-extendedtaskparameters) - * [Definition of new arguments](#definition-of-new-arguments) - * [Configuration files details](#configuration-files-details) - * [Global configuration structure](#global-configuration-structure) - * [Common configuration](#common-configuration) - * [QC Tasks configuration](#qc-tasks-configuration) - * [QC Checks configuration](#qc-checks-configuration) - * [QC Aggregators configuration](#qc-aggregators-configuration) - * [QC Post-processing configuration](#qc-post-processing-configuration) - * [External tasks configuration](#external-tasks-configuration) + * [Display a non-standard ROOT object in QCG](#display-a-non-standard-root-object-in-qcg) + * [Canvas options](#canvas-options) + * [Local QCG (QC GUI) setup](#local-qcg-qc-gui-setup) * [Miscellaneous](#miscellaneous) - * [Data Sampling monitoring](#data-sampling-monitoring) - * [Monitoring metrics](#monitoring-metrics) - * [Common check IncreasingEntries](#common-check-increasingentries) - * [Update the shmem segment size of a detector](#update-the-shmem-segment-size-of-a-detector) + * [Data Sampling monitoring](#data-sampling-monitoring) + * [Monitoring metrics](#monitoring-metrics) + * [Common check IncreasingEntries](#common-check-increasingentries) + * [Common check TrendCheck](#common-check-trendcheck) + * [Full configuration example](#full-configuration-example) + * [Update the shmem segment size of a detector](#update-the-shmem-segment-size-of-a-detector) + * [Readout chain (optional)](#readout-chain-optional) + * [Getting real data from readout](#getting-real-data-from-readout) + * [Readout data format as received by the Task](#readout-data-format-as-received-by-the-task) [← Go back to Post-processing](PostProcessing.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to Frequently Asked Questions →](FAQ.md) -# Framework - -## Plugging the QC to an existing DPL workflow - -Your existing DPL workflow can simply be considered a publisher. Therefore, replace `o2-qc-run-producer` with your own workflow. - -For example, if TPC wants to monitor the output `{"TPC", "CLUSTERS"}` of the workflow `o2-qc-run-tpcpid`, modify the config file to point to the correct data and do : - -``` -o2-qc-run-tpcpid | o2-qc --config json://${QUALITYCONTROL_ROOT}/etc/tpcQCPID.json -``` - -## Production of QC objects outside this framework - -QC objects (e.g. histograms) are typically produced in a QC task. -This is however not the only way. Some processing tasks such as the calibration -might have already processed the data and produced histograms that should be -monitored. Instead of re-processing and doing twice the work, one can simply -push this QC object to the QC framework where it will be checked and stored. - -### Configuration - -Let be a device in the main data flow that produces a histogram on a channel defined as `TST/HISTO/0`. To get this histogram in the QC and check it, add to the configuration file an "external device": - -```yaml - "externalTasks": { - "External-1": { - "active": "true", - "query": "External-1:TST/HISTO/0", "": "Query specifying where the objects to be checked and stored are coming from. Use the task name as binding. The origin (e.g. TST) is used as detector name for the objects." - } - }, - "checks": { -``` - -The "query" syntax is the same as the one used in the DPL and in the Dispatcher. It must match the output of another device, whether it is in the same workflow or in a piped one. -The `binding` (first part, before the colon) is used in the path of the stored objects and thus we encourage to use the task name to avoid confusion. Moreover, the `origin` (first element after the colon) is used as detectorName. - -### Example 1: basic - -As a basic example, we are going to produce histograms with the HistoProducer and collect them with the QC. The configuration is in [basic-external-histo.json](https://github.com/AliceO2Group/QualityControl/blob/master/Framework/basic-external-histo.json). An external task is defined and named "External-1" (see subsection above). It is then used in the Check QCCheck : - -```yaml - "QcCheck": { - "active": "true", - "className": "o2::quality_control_modules::skeleton::SkeletonCheck", - "moduleName": "QcSkeleton", - "policy": "OnAny", - "detectorName": "TST", - "dataSource": [{ - "type": "ExternalTask", - "name": "External-1", - "MOs": ["hello"] - }] - } -``` - -When using this feature, make sure that the name of the MO in the Check definition matches the name of the object you are sending from the external device. - -To run it, do: - -```yaml -o2-qc-run-histo-producer | o2-qc --config json://${QUALITYCONTROL_ROOT}/etc/basic-external-histo.json -``` - -The object is visible in the QCG or the CCDB at `qc/TST/MO/External-1/hello_0`. In general we publish the objects of an external device at `qc//MO//object`. - -The check results are stored at `qc//QO//object`. - -### Example 2: advanced - -This second, more advanced, example mixes QC tasks and external tasks. It is defined in [advanced-external-histo.json](https://github.com/AliceO2Group/QualityControl/blob/master/Framework/advanced-external-histo.json). It is represented here: - -![alt text](images/Advanced-external.png) - -First, it runs 1 QC task (QC-TASK-RUNNER-QcTask) getting data from a data producer (bottom boxes, typical QC worfklow). - -On top we see 3 histogram producers. `histoProducer-2` is not part of the QC, it is not an external device defined in the configuration file. The two other histogram producers are configured as external devices in the configuration file. - -`histoProducer-0` produces an object that is used in a check (`QcCheck-External-1`). `histoProducer-1` objects are not used in any check but we generate one automatically to take care of the storage in the database. - -To run it, do: - -```yaml -o2-qc-run-producer | o2-qc-run-histo-producer --producers 3 --histograms 3 | o2-qc --config json://${QUALITYCONTROL_ROOT}/etc/advanced-external-histo.json -``` - -### Limitations - -1. Objects sent by the external device must be either a TObject or a TObjArray. In the former case, the object will be sent to the checker encapsulated in a MonitorObject. In the latter case, each TObject of the TObjArray is encapsulated in a MonitorObject and is sent to the checker. - -## Multi-node setups - -During the data-taking Quality Control runs on a distributed computing system. Some QC Tasks are -executed on dedicated QC servers, while others run on FLPs and EPNs. In the first case, messages -coming from Data Sampling should reach QC servers where they are processed. In the latter case, -locally produced Monitor Objects should be merged on QC servers and then have Checks run on them. -By **remote QC tasks** we mean those which run on QC servers (**remote machines**), while **local QC Tasks** -run on FLPs and EPNs (**local machines**). - -Setting up a multinode setup to run standalone or with AliECS requires different amount of parameters, -as some of them are overwritten by AliECS anyway. Such parameters are marked accordingly. Please note -that for now we support cases with one or more local machines, but just only one remote machine. - -In our example, we assume having two local processing nodes (`localnode1`, `localnode2`) and one -QC node (`qcnode`). There are two types of QC Tasks declared: -* `MultiNodeLocal` which are executed on the local nodes and their results are merged and checked - on the QC server. -* `MultiNodeRemote` which runs on the QC server, receiving a small percent of data from - `localnode2` only. Mergers are not needed in this case, but there is a process running Checks against - Monitor Objects generated by this Task. - -We use the `SkeletonTask` class for both, but any Task can be used of course. Should a Task be local, -all its `MonitorObject`s need to be mergeable - they should be one of the mergeable ROOT types (histograms, TTrees) -or inherit [MergeInterface](https://github.com/AliceO2Group/AliceO2/blob/dev/Utilities/Mergers/include/Mergers/MergeInterface.h). - -These are the steps to follow to get a multinode setup: - -1. Prepare a configuration file. - -In this example we will use the `Framework/multiNode.json` config file. A config file should look -almost like the usual one, but with a few additional parameters. In case of a local task, these parameters should be -added: - -```json - "tasks": { - "MultiNodeLocal": { - "active": "true", - ... - "location": "local", - "localMachines": [ - "localnode1", - "localnode2" - ], - "remoteMachine": "qcnode", "":"not needed with FLP+QC, needed with EPN+QC", - "remotePort": "30132", "":"not needed with FLP+QC, needed with EPN+QC", - "localControl": "aliecs", "":"if absent, aliecs is default", - "mergingMode": "delta", "":"if absent, delta is default", - "mergersPerLayer": ["3", "1"], "":"if absent, one Merger is used" - } - }, -``` - -List the local processing machines in the `localMachines` array. `remoteMachine` should contain the host name which - will serve as a QC server and `remotePort` should be a port number on which Mergers will wait for upcoming MOs. Make - sure it is not used by other service. If different QC Tasks are run in parallel, use separate ports for each. The - `localControl` parameter allows to properly configure QC with respect to the control software it is run with. It can - be either `aliecs` (on FLPs) or `odc` (EPNs). It has no influence when running the software by hand. - -One also may choose the merging mode - `delta` is the default and recommended (tasks are reset after each cycle, so they - send only updates), but if it is not feasible, Mergers may expect `entire` objects - tasks are not reset, they - always send entire objects and the latest versions are combined in Mergers. - With the `delta` mode, one can cheat by specifying just one local machine name and using only that one during execution. - This is not possible with `entire` mode, because then Mergers need identifiable data sources to merge objects correctly. - If one merger process is not enough to sustain the input data throughput, one may define multiple Merger layers with - `mergersPerLayer` option. - -In case of a remote task, choosing `"remote"` option for the `"location"` parameter is needed. In standalone setups -and those controlled by ODC, one should also specify the `"remoteMachine"`, so sampled data reaches the right node. -Also, `"localControl"` should be specified to generate the correct AliECS workflow template. - -```json - "tasks": { - ... - "MultiNodeRemote": { - "active": "true", - ... - "dataSource": { - "type": "dataSamplingPolicy", - "name": "rnd-little" - }, - "taskParameters": {}, - "location": "remote", - "remoteMachine": "qcnode", "":"not needed with FLP+QC, needed with EPN+QC", - "localControl": "aliecs", "":"aliecs is default, not needed with FLP+QC, needed with EPN+QC" - } - } -``` - -In case the task is running remotely, data should be sampled. The minimal-effort approach requires adding a port number - (see the example below). Use separate ports for each Data Sampling Policy. If the same configuration file will be used - on many nodes, but only some of them should apply a given sampling policy, one should also specify the list of - machines to match (or generalized aliases, e.g. "flp", "epn"). - -```json -{ - "dataSamplingPolicies": [ - ... - { - "id": "rnd-little", - "active": "true", - "machines": [ "","only needed when the policy should run on a subgroup of nodes", - "localnode2" - ], - "port": "30333", "":"compulsory on standalone and ODC setups (EPN), not needed for FLPs", - ... - } - ] -} -``` - -By default, the channel is bound on the QC Task side. If this is not what you need, add `"bindLocation" : "local"` in -the policy configuration (`"remote"` is the default value) and make sure to use valid host names. - -2. Make sure that the firewalls are properly configured. If your machines block incoming/outgoing connections by - default, you can add these rules to the firewall (run as sudo). Consider enabling only concrete ports or a small - range of those. - -``` -# localnode1 and localnode2 : -iptables -I INPUT -p tcp -m conntrack --ctstate NEW,ESTABLISHED -s qcnode -j ACCEPT -iptables -I OUTPUT -p tcp -m conntrack --ctstate NEW,ESTABLISHED -d qcnode -j ACCEPT -# qcnode: -iptables -I INPUT -p tcp -m conntrack --ctstate NEW,ESTABLISHED -s localnode1 -j ACCEPT -iptables -I OUTPUT -p tcp -m conntrack --ctstate NEW,ESTABLISHED -d localnode1 -j ACCEPT -iptables -I INPUT -p tcp -m conntrack --ctstate NEW,ESTABLISHED -s localnode2 -j ACCEPT -iptables -I OUTPUT -p tcp -m conntrack --ctstate NEW,ESTABLISHED -d localnode2 -j ACCEPT -``` - -If your network is isolated, you might consider disabling the firewall as an alternative. Be wary of the security risks. - -``` -systemctl stop firewalld # to disable until reboot -systemctl disable firewalld # to disable permanently -``` - -3. Install the same version of the QC software on each of these nodes. We cannot guarantee that different QC versions will talk to each other without problems. Also, make sure the configuration file that you will use is the same everywhere. - -4. Run each part of the workflow. In this example `o2-qc-run-producer` represents any DPL workflow, here it is just a process which produces some random data. -The `--host` argument is matched against the `machines` lists in the configuration files. - -``` -# On localnode1: -o2-qc-run-producer | o2-qc --config json:/${QUALITYCONTROL_ROOT}/etc/multiNode.json --local --host localnode1 -b -# On localnode2: -o2-qc-run-producer | o2-qc --config json:/${QUALITYCONTROL_ROOT}/etc/multiNode.json --local --host localnode2 -b -# On qcnode: -o2-qc --config json:/${QUALITYCONTROL_ROOT}/etc/multiNode.json --remote -``` - -If there are no problems, on QCG you should see the `example` histogram updated under the paths `qc/TST/MO/MultiNodeLocal` -and `qc/TST/MO/MultiNodeRemote`, and corresponding Checks under the path `qc/TST/QO/`. - -When using AliECS, one has to generate workflow templates and upload them to the corresponding repository. Please -contact the QC or AliECS developers to receive assistance or instructions on how to do that. - -## Batch processing - -In certain cases merging results of parallel QC Tasks cannot be performed in form of message passing. -An example of this are the simulation workflows, which exchange data between processing stages via files - and produce (and process) consecutive TimeFrames in different directories in parallel. -Then, one can run QC Tasks on incomplete data and save the results to a file. -If the file already exists, the new objects will be merged with those obtained so far. -At the end, one can run the rest of processing chain (Checks, Aggregators) on the complete objects. - -Here is a simple example: - -```bash -# Remove any existing results -rm results.root -# Run the Tasks 3 times, merge results into the file. -o2-qc-run-producer --message-amount 100 | o2-qc --config json:/${QUALITYCONTROL_ROOT}/etc/basic.json --local-batch results.root -o2-qc-run-producer --message-amount 100 | o2-qc --config json:/${QUALITYCONTROL_ROOT}/etc/basic.json --local-batch results.root -o2-qc-run-producer --message-amount 100 | o2-qc --config json:/${QUALITYCONTROL_ROOT}/etc/basic.json --local-batch results.root -# Run Checks and Aggregators, publish results to QCDB -o2-qc --config json:/${QUALITYCONTROL_ROOT}/etc/basic.json --remote-batch results.root -``` - -Please note, that the local batch QC workflow should not work on the same file at the same time. -A semaphore mechanism is required if there is a risk they might be executed in parallel. - -The file is organized into directories named after 3-letter detector codes and sub-directories representing Monitor Object Collections for specific tasks. -To browse the file, one needs the associated Quality Control environment loaded, since it contains QC-specific data structures. -It is worth remembering, that this file is considered as intermediate storage, thus Monitor Object do not have Checks applied and cannot be considered the final results. -The quick and easy way to inspect the contents of the file is to load a recent environment (e.g. on lxplus) and open it with ROOT's `TBrowser`: - -```shell -alienv enter O2PDPSuite/nightly-20221219-1 -root -TBrowser t; // a browser window will pop-up -``` - -...or by browsing the file manually: - -```shell -alienv enter O2PDPSuite/nightly-20221219-1 -root -root [0] auto f = new TFile("QC_fullrun.root") -(TFile *) @0x7ffe84833dc8 -root [1] f->ls() -TFile** QC_fullrun.root - TFile* QC_fullrun.root - KEY: TDirectoryFile CPV;1 CPV - KEY: TDirectoryFile EMC;1 EMC - KEY: TDirectoryFile FDD;1 FDD - KEY: TDirectoryFile FT0;1 FT0 - KEY: TDirectoryFile FV0;1 FV0 - KEY: TDirectoryFile GLO;1 GLO - KEY: TDirectoryFile ITS;1 ITS -... -root [2] f->cd("GLO") -(bool) true -root [3] f->ls() -TFile** QC_fullrun.root - TFile* QC_fullrun.root - TDirectoryFile* GLO GLO - KEY: o2::quality_control::core::MonitorObjectCollection MTCITSTPC;1 - KEY: o2::quality_control::core::MonitorObjectCollection Vertexing;1 - KEY: TDirectoryFile CPV;1 CPV -... -root [4] auto vtx = dynamic_cast(f->Get("GLO/Vertexing")) -(o2::quality_control::core::MonitorObjectCollection *) @0x7ffe84833dc8 -root [5] auto vtx_x = dynamic_cast(vtx->FindObject("vertex_X")) -(o2::quality_control::core::MonitorObject *) @0x7ffe84833dc8 -root [6] vtx_x->getObject()->ClassName() -(const char *) "TH1F" -``` - -To merge several incomplete QC files, one can use the `o2-qc-file-merger` executable. -It takes a list of input files, which may or may not reside on alien, and produces a merged file. -One can select whether the executable should fail upon any error or continue for as long as possible. -Please see its `--help` output for usage details. - -## Moving window - -### Moving window for all plots generated by a task - -By default QC Tasks are never reset, thus the MOs they produce contain data from the full run. -However, if objects should have a shorter validity range, one may add the following options to QC Task configuration: - -```json - "MovingWindowTaskA": { - ... - "resetAfterCycles": "10", - } -``` - -In the case above the QC Task will have the `TaskInterface::reset()` method invoked each 10 cycles. -Thus, all the plots generated by this task will by affected. - -If the QC Task runs in parallel on many nodes and its results are merged, the effects will be different -depending on the chosen merging mode: -* If `"delta"` mode is used, the Merger in the last layer will implement the moving window, while the QC Tasks will - still reset after each cycle. Please note, that QC Tasks will fall out of sync during data acquisition, so the moving - window might contain slightly misaligned data time ranges coming from different sources. Also, due to fluctuations of - the data transfer, objects coming from different sources might appear more frequently than others. Thus, one might - notice higher occupancy on stave A one time, but the next object might contain less than average data for the same stave. -* In the `"entire"` mode, QC Tasks will reset MOs, while Mergers will use the latest available object version from each - Task. Please note that if one of the Tasks dies, an old version of MO will be still used over and over. Thus, `"delta"` - mode is advised in most use cases. - -In setups with Mergers one may also extend the Mergers cycle duration, which can help to even out any data fluctuations: - -```json - "MovingWindowTaskB": { - ... - "cycleDurationSeconds" : "60", - "mergingMode" : "delta", - "mergerCycleMultiplier": "10", "": "multiplies cycleDurationSeconds in Mergers", - "resetAfterCycles": "1", "": "it could be still larger than 1" - } - ``` - -In the presented case, the Merger will publish one set of complete MOs per 10 minutes, which should contain all deltas - received during this last period. Since the QC Tasks cycle is 10 times shorter, the occupancy fluctuations should be - less apparent. Please also note, that using this parameter in the `"entire"` merging mode does not make much sense, - since Mergers would use every 10th incomplete MO version when merging. - -### Moving windows of selected plots only - -The following applies to synchronous setups which use Mergers in the delta mode and all asynchronous setups. -One can obtain objects containing data from one cycle alongside the ones covering the whole run. -These are saved in QCDB in the task subdirectory `mw` and also can be requested by Checks. -To specify which objects should get a moving window variant, add a `"movingWindows"` list to the task configuration: - -```json - "MyTask": { - ... - "cycleDurationSeconds" : "60", - "mergingMode" : "delta", - "movingWindows" : [ "plotA", "plotB" ] - } -``` - -To request these objects in a Check, use `TaskMovingWindow` data source, as in the example: - -```json - "QcCheckMW": { - "dataSource": [{ - "type": "TaskMovingWindow", - "name": "MyTask", - "MOs": ["plotA"], "": "MOs can be omitted if all moving windows of a task are requested" - }] - } -``` - -It is possible to request both the integrated and single cycle plots by the same Check. - -To test it in a small setup, one can run `o2-qc` with `--full-chain` flag, which creates a complete workflow with a Merger for **local** QC tasks, even though it runs just one instance of them. -Please remember to use `"location" : "local"` in such case. - -In asynchronous QC, the moving window plots will appear in the intermediate QC file in the directory `mw` and will be uploaded to QCDB to `/mw`. -When testing, please make sure to let DPL know that it has to run in Grid mode, so that QC can compute object validity based on timestamps in the data: - -``` -export O2_DPL_DEPLOYMENT_MODE=Grid && o2-qc --local-batch QC.root ... -``` - -## Monitor cycles - -The QC tasks monitor and process data continuously during a so-called "monitor cycle". At the end of such a cycle they publish the QC objects that will then continue their way in the QC data flow. - -A monitor cycle lasts typically between **1 and 5 minutes**, some reaching 10 minutes but never less than 1 minute for performance reasons. -It is defined in the config file this way: - -``` - "tasks": { - "dataSizeTask": { - "cycleDurationSeconds": "60", - ... -``` - -It is possible to specify various durations for different period of times. It is particularly useful to have shorter cycles at the beginning of the run and longer afterwards: - -``` - "tasks": { - "dataSizeTask": { - "cycleDurations": [ - {"cycleDurationSeconds": 60, "validitySeconds": 300}, - {"cycleDurationSeconds": 180, "validitySeconds": 600}, - {"cycleDurationSeconds": 300, "validitySeconds": 1} - ], - ... -``` - -In this example, a cycle of 60 seconds is used for the first 5 minutes (300 seconds), then a cycle of 3 minutes (180 seconds) between 5 minutes and 10 minutes after SOR, and finally a cycle of 5 minutes for the rest of the run. The last `validitySeconds` is not used and is just applied for the rest of the run. - -## Writing a DPL data producer - -For your convenience, and although it does not lie within the QC scope, we would like to document how to write a simple data producer in the DPL. The DPL documentation can be found [here](https://github.com/AliceO2Group/AliceO2/blob/dev/Framework/Core/README.md) and for questions please head to the [forum](https://alice-talk.web.cern.ch/). - -As an example we take the `DataProducerExample` that you can find in the QC repository. It is produces a number. By default it will be 1s but one can specify with the parameter `my-param` a different number. It is made of 3 files : - -* [runDataProducerExample.cxx](../Framework/src/runDataProducerExample.cxx) : - This is an executable with a basic data producer in the Data Processing Layer. - There are 2 important functions here : - * `customize(...)` to add parameters to the executable. Note that it must be written before the includes for the dataProcessing. - * `defineDataProcessing(...)` to define the workflow to be ran, in our case the device(s) publishing the number. -* [DataProducerExample.h](../Framework/include/QualityControl/DataProducerExample.h) : - The key elements are : - 1. The include `#include ` - 2. The function `getDataProducerExampleSpec(...)` which must return a `DataProcessorSpec` i.e. the description of a device (name, inputs, outputs, algorithm) - 3. The function `getDataProducerExampleAlgorithm` which must return an `AlgorithmSpec` i.e. the actual algorithm that produces the data. -* [DataProducerExample.cxx](../Framework/src/DataProducerExample.cxx) : - This is just the implementation of the header described just above. You will probably want to modify `getDataProducerExampleSpec` and the inner-most block of `getDataProducerExampleAlgorithm`. You might be taken aback by the look of this function, if you don't know what a _lambda_ is just ignore it and write your code inside the accolades. - -You will probably write it in your detector's O2 directory rather than in the QC repository. - -## Custom merging - -When needed, one may define their own algorithm to merge a Monitor Object. -To do so, inherit the [MergeInterface](https://github.com/AliceO2Group/AliceO2/blob/dev/Utilities/Mergers/include/Mergers/MergeInterface.h) class and override the corresponding methods. -Please pay special attention to delete all the allocated resources in the destructor to avoid any memory leaks. -Feel free to consult the existing usage examples among other modules in the QC repository. - -Once a custom class is implemented, one should let QCG know how to display it correctly, which is explained in the subsection [Display a non-standard ROOT object in QCG](#display-a-non-standard-root-object-in-qcg). - -## Critical, resilient and non-critical tasks - -DPL devices can be marked as expendable, resilient or critical. Expendable tasks can die without affecting the run. -Resilient tasks can survive having one or all their inputs coming from an expendable task but they will stop the system if they themselves die. -Critical tasks (default) will stop the system if they die and will not accept input from expendable tasks. - -In QC we use these `labels`. - -### QC tasks - -In QC, one can mark a task as critical or non-critical: - -```json - "tasks": { - "QcTask": { - "active": "true", - "critical": "false", "": "if false the task is allowed to die without stopping the workflow, default: true", -``` - -By default they are `critical` meaning that their failure will stop the run. -If they are not critical, they will be `expendable` and will not stop the run if they die. - -### Auto-generated proxies - -They adopt the criticality of the task they are proxying. - -### QC mergers - -Mergers are `resilient`. - -### QC check runners - -CheckRunners are `resilient`. - -### QC aggregators - -Aggregators are `resilient`. - -### QC post-processing tasks - -Post-processing tasks can be marked as critical or non-critical: - -```json - "postprocessing": { - "ExamplePostprocessing": { - "active": "true", - "critical": "false", "": "if false the task is allowed to die without stopping the workflow, default: true", -``` - -By default, they are critical meaning that their failure will stop the run. -If they are not critical, they will be `expendable` and will not stop the run if they die. - -## QC with DPL Analysis - -### 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 by default the executable will ignore the directory structure in the input file and upload all objects to one directory. -If you need the directory structure preserved, add the argument `--preserve-directories`. - -## Propagating Check results to RCT in Bookkeeping - -The framework allows to propagate Quality Objects (QOs) produced by Checks and Aggregators to RCT in Bookkeeping. -The synchronisation is done once, at the end of workflow runtime, i.e. at the End of Run or in the last stage of QC merging on Grid. -Propagation can be enabled by adding the following key-value pair to Check/Aggregator configuration: - -```json - "exportToBookkeeping": "true" -``` - -Using it for Aggregators is discouraged, as the information on which exact Check failed is lost or at least obfuscated. - -Also, make sure that the configuration file includes the Bookkeeping URL. - -Check results are converted into Flags, which are documented in [O2/DataFormats/QualityControl](https://github.com/AliceO2Group/AliceO2/tree/dev/DataFormats/QualityControl). -Information about the object validity is preserved, which allows for time-based flagging of good/bad data. - -### Conversion details - -Below we describe some details of how the conversion is done. -Good QOs are marked with green, Medium QOs are marked with orange and Bad QOs are marked with red. -Null QOs are marked with purple. - -* **Good QOs with no Flags associated are not converted to any Flags.** - According to the preliminary design for Data Tagging, "bad" Flags always win, thus there is no need for explicit "good" Flags. - It also implies that there is no need to explicitly add Good Flag to Good Quality. - -![](images/qo_flag_conversion_01.svg) - -* **Bad and Medium QOs with no Flags are converted to Flag 14 (Unknown).** - This means that Medium Quality data is by default bad for Analysis. - -![](images/qo_flag_conversion_02.svg) - -* **Null QOs with no Flags are converted to Flag 1 (Unknown Quality).** - -![](images/qo_flag_conversion_03.svg) - -* **All QOs with Flags are converted to Flags, while the Quality is ignored.** - As a consequence, one can customize the meaning of any Quality (Medium in particular) in terms of data usability. - A warning is printed if a Check associates a good Flag to bad Quality or a bad Flag to good Quality. - -![](images/qo_flag_conversion_04.svg) - -* **Timespans not covered by a given QO are filled with Flag 1 (Unknown Quality).** - In other words, if an object was missing during a part of the run, we can state that the data quality is not known. - -![](images/qo_flag_conversion_05.svg) - -* **Overlapping or adjacent Flags with the same ID, comment and source (QO name) are merged.**. - This happens even if they were associated with different Qualities, e.g. Bad and Medium. - Order of Flag arrival does not matter. - -![](images/qo_flag_conversion_06.svg) -![](images/qo_flag_conversion_07.svg) - -* **Flag 1 (Unknown Quality) is overwritten by any other Flag.** - This allows us to return Null Quality when there is not enough statistics to determine data quality, but it can be suppressed later, once we can return Good/Medium/Bad. - -![](images/qo_flag_conversion_08.svg) - -* **Good and Bad flags do not affect each other, they may coexist.** - -![](images/qo_flag_conversion_09.svg) - -* **Flags for different QOs (QO names) do not affect each other. - Flag 1 (Unknown Quality) is added separately for each.** - -![](images/qo_flag_conversion_10.svg) - -# Solving performance issues - -Problems with performance in message passing systems like QC usually manifest in backpressure seen in input channels of processes which are too slow. -QC processes usually use one worker thread, thus one can also observe that they use a full CPU core when struggling to consume incoming data. -When observing performance issues with QC setups, consider the following actions to improve it. - -## Dispatcher - -Dispatcher will usually cause backpressure when it is requested to sample too much data. -In particular, copying many small messages takes more time than less messages of equivalent size. -To improve the performance: -* reduce the sampling rate -* disable unused sampling policies -* adapt the data format to pack data in fewer messages -* when in need of 100% data, do not use Data Sampling, but connect to the data source directly - -## QC Tasks - -QC Tasks are implemented by the users, thus the maximum possible input data throughput largely depends on the task implementation. -If a QC Task cannot cope with the input messages, consider: -* sampling less data -* using performance measurement tools (like `perf top`) to understand where the task spends the most time and optimize this part of code -* if one task instance processes data, spawn one task per machine and merge the result objects instead - -## Mergers - -The performance of Mergers depends on the type of objects being merged, as well as their number and size. -The following points might help avoid backpressure: -* increase QC tasks cycle duration -* use less or smaller objects -* if an object has its custom Merge() method, check if it could be optimized -* enable multi-layer Mergers to split the computations across multiple processes (config parameter "mergersPerLayer") - -# Understanding and reducing memory footprint - -When developing a QC module, please be considerate in terms of memory usage. -Large histograms could be optionally enabled/disabled depending on the context that the QC is ran. -Investigate if reducing the bin size (e.g. TH2D to TH2F) would still provide satisfactory results. -Consider loading only the parts of detector geometry which are being used by a given task. - -## Analysing memory usage with valgrind - -0) Install valgrind, if not yet installed - -1) Run the QC workflow with argument `--child-driver 'valgrind --tool=massif'` (as well as any file reader / processing workflow you need to obtain data in QC) - -2) The workflow will run and save files massif.out. - -3) Generate a report for the file corresponding to the PID of the QC task: - -``` -ms_print massif.out.976329 > massif_abc_task.log -``` - -4) The generated report contains: -* the command used to run the process -* graph of the memory usage -* grouped call stacks of all memory allocations on the heap (above certain threshold) within certain time intervals. - The left-most call contains all the calls which lead to it, represented on the right. - For example, the call stack below means that the AbcTask created a TH2F histogram in the initalize method at the line - AbcTask.cxx:82, which was 51,811,760B. In total, 130,269,568B worth of TH2F histograms were created in this time interval. - -``` -98.56% (256,165,296B) (heap allocation functions) malloc/new/new[], --alloc-fns, etc. -->50.12% (130,269,568B) 0xFCBD1A6: TArrayF::Set(int) [clone .part.0] (TArrayF.cxx:111) -| ->50.12% (130,269,568B) 0xEC1DB1C: TH2F::TH2F(char const*, char const*, int, double, double, int, double, double) (TH2.cxx:3573) -| ->19.93% (51,811,760B) 0x32416518: make_unique (unique_ptr.h:1065) -| | ->19.93% (51,811,760B) 0x32416518: o2::quality_control_modules::det::AbcTask::initialize(o2::framework::InitContext&) (AbcTask.cxx:82) -``` - -5) To get a lightweight and more digestible output, consider running the massif report through the following command to get the summary of the calls only within a QC module. This essentially tells you how much memory a given line allocates. - -``` -[O2PDPSuite/latest] ~/alice/test-rss $> grep quality_control_modules massif_abc_task.log | sed 's/^.*[0-9][0-9]\.[0-9][0-9]\% //g' | sort | uniq -(242,371,376B) 0x324166B2: o2::quality_control_modules::det::AbcTask::initialize(o2::framework::InitContext&) (AbcTask.cxx:88) -(4,441,008B) 0x3241633F: o2::quality_control_modules::det::AbcTask::initialize(o2::framework::InitContext&) (AbcTask.cxx:76) -(4,441,008B) 0x32416429: o2::quality_control_modules::det::AbcTask::initialize(o2::framework::InitContext&) (AbcTask.cxx:79) -(51,811,760B) 0x32416518: o2::quality_control_modules::det::AbcTask::initialize(o2::framework::InitContext&) (AbcTask.cxx:82) -(51,811,760B) 0x324165EB: o2::quality_control_modules::det::AbcTask::initialize(o2::framework::InitContext&) (AbcTask.cxx:85) -``` - -6) Consider reducing the size and number of the biggest histogram. Consider disabling histograms which will not be useful for async QC (no allocations, no startPublishing). # CCDB / QCDB @@ -1033,536 +291,6 @@ These methods must be called after the objects has been published, i.e. after th To install and run the QCG locally please follow these instructions : -# FLP Suite - -The QC is part of the FLP Suite. The Suite is installed on FLPs through RPMs and is configured with ansible. As a consequence a few things are different in this context compared to a pure development setup. - -## Developing QC modules on a machine with FLP suite - -Development RPMs are available on the FLPs. Start by installing them, then compile QC and finally tell aliECS to use it. - -**Installation** - -As root do: - -``` -yum install o2-QualityControl-devel git -y -``` - -**Compilation** - -As user `flp` do: - -``` -git clone https://github.com/AliceO2Group/QualityControl.git -cd QualityControl -git checkout # use the release included in the installed FLP suite -mkdir build -cd build -mkdir /tmp/installdir -cmake -DCMAKE_INSTALL_PREFIX=/tmp/installdir -G Ninja -DCLANG_EXECUTABLE=/opt/o2/bin-safe/clang -DCMAKE_BUILD_TYPE=RelWithDebugInfo .. -ninja -j16 install -``` - -_**Compilation on top of a local O2**_ - -If you want to build also O2 locally do - -``` -# O2 -git clone https://github.com/AliceO2Group/AliceO2.git -cd AliceO2 -git checkout # use the release included in the installed FLP suite -mkdir build -cd build -cmake -DCMAKE_INSTALL_PREFIX=/tmp/installdir -G Ninja -DCLANG_EXECUTABLE=/opt/o2/bin-safe/clang -DCMAKE_BUILD_TYPE=RelWithDebugInfo .. -ninja -j8 install - -# QC -git clone https://github.com/AliceO2Group/QualityControl.git -cd QualityControl -git checkout # use the release included in the installed FLP suite -mkdir build -cd build -cmake -DCMAKE_INSTALL_PREFIX=/tmp/installdir -G Ninja -DCLANG_EXECUTABLE=/opt/o2/bin-safe/clang -DCMAKE_BUILD_TYPE=RelWithDebugInfo -DO2_ROOT=/tmp/installdir .. -ninja -j8 install -``` - -_**Important step in case several nodes are involved**_ - -In case the workflows will span over several FLPs and/or QC machines, one should `scp` the `installdir` to the other machines in the same directory. - -**Use it in aliECS** - -In the aliECS gui, in the panel "Advanced Configuration", et an extra variable `extra_env_vars` and set it to - -``` -PATH=/tmp/installdir/bin/:$PATH; LD_LIBRARY_PATH=/tmp/installdir/lib/:/tmp/installdir/lib64/:$LD_LIBRARY_PATH; QUALITYCONTROL_ROOT=/tmp/installdir/; echo -``` - -Replace `/tmp/installdir` with your own path. Make sure that the directory is anyway readable and traversable by users `flp` and `qc` - -## Switch detector in the workflow _readout-dataflow_ - -The workflow readout-dataflow works by default with the detector code TST. To run with another detector (e.g. EMC) do: - -2. Replace all instances of `TST` in the QC config file in consul with the one of the detector (e.g. `EMC`). -2. Set the variable `detector` in aliECS to the detector (e.g. `EMC`). - -## Get all the task output to the infologger - -Set the variable log_task_output=all - -## Using a different config file with the general QC - -One can set the `QC URI` to a different config file that is used by the general QC when enabled. However, this is not the recommended way. One must make sure that the name of the task and the check are left untouched and that they are both enabled. - -## Enable the repo cleaner - -If the CCDB used in an FLP setup is the local one, the repo cleaner might be necessary as to avoid filling up the disk of the machine. - -By defaults there is a _disabled_ cron job : - -```shell -*/10 * * * * /opt/o2/bin/o2-qc-repo-cleaner --config /etc/flp.d/ccdb-sql/repocleaner.yaml --dry-run > /dev/null 2>> /tmp/cron-errors.txt -``` - -1. copy the config file /etc/flp.d/ccdb-sql/repocleaner.yaml -2. modify the config file to suit your needs -3. run by hand the repo-cleaner to check that the config file is ok -3. update the cron job to use the modified config file -4. uncomment the cron job - -# Configuration - -## Merging multiple configuration files into one - -To merge multiple QC configuration files into one, one can use `jq` in the following way: - -``` -jq -n 'reduce inputs as $s (input; .qc.tasks += ($s.qc.tasks) | .qc.checks += ($s.qc.checks) | .qc.externalTasks += ($s.qc.externalTasks) | .qc.postprocessing += ($s.qc.postprocessing)| .dataSamplingPolicies += ($s.dataSamplingPolicies))' $QC_JSON_GLOBAL $JSON_FILES > $MERGED_JSON_FILENAME -``` - -However, one should pay attention to avoid duplicate task definition keys (e.g. having RawTask twice, each for a different detector), otherwise only one of them would find its way to a merged file. -In such case, one can add the `taskName` parameter in the body of a task configuration structure to use the preferred name and change the root key to a unique id, which shall be used only for the purpose of navigating a configuration file. -If `taskName` does not exist, it is taken from the root key value. -Please remember to update also the references to the task in other actors which refer it (e.g. in Check's data source). - -These two tasks will **not** be merged correctly: - -```json - "RawTask": { - "className": "o2::quality_control_modules::abc::RawTask", - "moduleName": "QcA", - "detectorName": "A", - "dataSource": { - "type": "dataSamplingPolicy", - "name": "raw-a" - } - } -``` - -```json - "RawTask": { - "className": "o2::quality_control_modules::xyz::RawTask", - "moduleName": "QcB", - "detectorName": "B", - "dataSource": { - "type": "dataSamplingPolicy", - "name": "raw-b" - } - } -``` - -The following tasks will be merged correctly: - -```json - "RawTaskA": { - "taskName": "RawTask", - "className": "o2::quality_control_modules::abc::RawTask", - "moduleName": "QcA", - "detectorName": "A", - "dataSource": { - "type": "dataSamplingPolicy", - "name": "raw-a" - } - } -``` - -```json - "RawTaskB": { - "taskName": "RawTask" - "className": "o2::quality_control_modules::xyz::RawTask", - "moduleName": "QcB", - "detectorName": "B", - "dataSource": { - "type": "dataSamplingPolicy", - "name": "raw-b" - } - } -``` - -The same approach can be applied to other actors in the QC framework, like Checks (`checkName`), Aggregators(`aggregatorName`), External Tasks (`taskName`) and Postprocessing Tasks (`taskName`). - -## Templating config files - -> [!WARNING] -> Templating only works when using aliECS, i.e. in production and staging. - -The templating is provided by a template engine called `jinja`. You can use any of its feature. A couple are described below and should satisfy the vast majority of the needs. - -### Preparation - -> [!IMPORTANT] -> Workflows have already been migrated to apricot. This should not be needed anymore. - -To template a config file, modify the corresponding workflow in `ControlWorkflows`. This is needed because we won't use directly `Consul` but instead go through `apricot` to template it. - -1. Replace `consul-json` by `apricot` -2. Replace `consul_endpoint` by `apricot_endpoint` -3. Make sure to have single quotes around the URI - -Example: - -``` -o2-qc --config consul-json://{{ consul_endpoint }}/o2/components/qc/ANY/any/mch-qcmn-epn-full-track-matching --remote -b -``` - -becomes - -``` -o2-qc --config 'apricot://{{ apricot_endpoint }}/o2/components/qc/ANY/any/mch-qcmn-epn-full-track-matching' --remote -b -``` - -Make sure that you are able to run with the new workflow before actually templating. - -### Include a config file - -To include a config file (e.g. named `mch_digits`) add this line : - -``` -{% include "MCH/mch_digits" %} -``` - -The content of the file `mch_digits` is then copied into the config file. Thus make sure that you include all the commas and stuff. - -#### Configuration files organisation - -Once you start including files, you must put the included files inside the corresponding detector subfolder (that have already been created for you). - -Common config files includes are provided in the `COMMON` subfolder. - -### Conditionals - -The `if` looks like - -``` -{% if [condition] %} … {% endif %} -``` - -The condition probably requires some external info, such as the run type or a detectors list. Thus you must pass the info in the ControlWorkflows. - -It could look like this - -``` -o2-qc --config 'apricot://{{ apricot_endpoint }}/o2/components/qc/ANY/any/tpc-pulser-calib-qcmn?run_type={{ run_type }}' ... -``` - -or - -``` -o2-qc --config 'apricot://{{ apricot_endpoint }}/o2/components/qc/ANY/any/mch-qcmn-epn-full-track-matching?detectors={{ detectors }}' ... -``` - -Then use it like this: - -``` -{% if run_type == "PHYSICS" %} -... -{% endif %} -``` - -or like this respectively: - -``` -{% if "mch" in detectors|lower %} -... -{% endif %} -``` - -### Test and debug - -To see how a config file will look like once templated, simply open a browser at this address: `{{apricot_endpoint}}/components/qc/ANY/any/tpc-pulser-calib-qcmn?process=true` -Replace `{{apricot_endpoint}}` by the value you can find in Consul under `o2/runtime/aliecs/vars/apricot_endpoint` (it is different on staging and prod). -_Note that there is no `o2` in the path!!!_ - -### Example - -We are going to create in staging a small example to demonstrate the above. -First create 2 files if they don't exist yet: - -**o2/components/qc/ANY/any/templating_demo** - -``` -{ - "qc": { - "config": {% include "TST/templating_included" %} - {% if run_type == "PHYSICS" %} ,"aggregators": "included" {% endif %} - } -} -``` - -Here we simply include 1 file from a subfolder and add a piece if a certain condition is successful. - -**o2/components/qc/ANY/any/TST/templating_included** - -``` -{ - bookkeeping": { - "url": "alio2-cr1-hv-web01.cern.ch:4001" - } -} -``` - -And now you can try it out: - -``` -http://alio2-cr1-hv-mvs00.cern.ch:32188/components/qc/ANY/any/templating_demo?process=true -``` - ---> the file is included inside the other. - -``` -[http://alio2-cr1-hv-mvs00.cern.ch:32188/components/qc/ANY/any/templating_demo?process=true](http://alio2-cr1-hv-mvs00.cern.ch:32188/components/qc/ANY/any/templating_demo?process=true&run_type=PHYSICS) -``` - ---> the file is included and the condition is true thus we have an extra line. - -## Definition and access of simple user-defined task configuration ("taskParameters") - -The new, extended, way of defining such parameters, not only in Tasks but also in Checks, Aggregators and PP tasks, -is described in the next section. - -A task can access custom parameters declared in the configuration file at `qc.tasks..taskParameters`. They are stored inside an object of type `CustomParameters` named `mCustomParameters`, which is a protected member of `TaskInterface`. - -The syntax is - -```json - "tasks": { - "QcTask": { - "taskParameters": { - "myOwnKey1": "myOwnValue1" - }, -``` - -It is accessed with : `mCustomParameters["myOwnKey"]`. - -## Definition and access of user-defined configuration ("extendedTaskParameters") - -User code, whether it is a Task, a Check, an Aggregator or a PostProcessing task, can access custom parameters declared in the configuration file. -They are stored inside an object of type `CustomParameters` named `mCustomParameters`, which is a protected member of `TaskInterface`. - -The following table gives the path in the config file and the name of the configuration parameter for the various types of user code: - -| User code | Config File item | -|----------------|--------------------------------------------------------| -| Task | `qc.tasks..extendedTaskParameters` | -| Check | `qc.checks..extendedCheckParameters` | -| Aggregator | `qc.aggregators..extendedAggregatorParameters` | -| PostProcessing | `qc.postprocessing..extendedTaskParameters` | - -The new syntax is - -```json - "tasks": { - "QcTask": { - "extendedTaskParameters": { - "default": { - "default": { - "myOwnKey1": "myOwnValue1", - "myOwnKey2": "myOwnValue2", - "myOwnKey3": "myOwnValue3" - } - }, - "PHYSICS": { - "default": { - "myOwnKey1": "myOwnValue1b", - "myOwnKey2": "myOwnValue2b" - }, - "pp": { - "myOwnKey1": "myOwnValue1c" - }, - "PbPb": { - "myOwnKey1": "myOwnValue1d" - } - }, - "COSMICS": { - "myOwnKey1": "myOwnValue1e", - "myOwnKey2": "myOwnValue2e" - } - }, -``` - -It allows to have variations of the parameters depending on the run and beam types. The proper run types can be found here: [ECSDataAdapters.h](https://github.com/AliceO2Group/AliceO2/blob/dev/DataFormats/Parameters/include/DataFormatsParameters/ECSDataAdapters.h#L54). The `default` can be used -to ignore the run or the beam type. -The beam type comes from the parameter `pdp_beam_type` set by ECS and can be one of the following: `pp`, `PbPb`, `pPb`, `pO`, `OO`, `NeNe`, `cosmic`, `technical`. -See `[readout-dataflow](https://github.com/AliceO2Group/ControlWorkflows/blob/master/workflows/readout-dataflow.yaml)` to verify the possible values. - -The values can be accessed in various ways described in the following sub-sections. - -### Access optional values with or without activity - -The value for the key, runType and beamType is returned if found, or an empty value otherwise. -However, before returning an empty value we try to substitute the runType and the beamType with "default". - -```c++ -// returns an Optional if it finds the key `myOwnKey` for the runType and beamType of the provided activity, -// or if it can find the key with the runType or beamType substituted with "default". -auto param = mCustomParameters.atOptional("myOwnKey1", activity); // activity is "PHYSICS", "PbPb" , returns "myOwnValue1d" -// same but passing directly the run and beam types -auto param = mCustomParameters.atOptional("myOwnKey1", "PHYSICS", "PbPb"); // returns "myOwnValue1d" -// or with only the run type -auto param = mCustomParameters.atOptional("myOwnKey1", "PHYSICS"); // returns "myOwnValue1b" -``` - -### Access values directly specifying the run and beam type or an activity - -The value for the key, runType and beamType is returned if found, or an exception is thrown otherwise.. -However, before throwing we try to substitute the runType and the beamType with "default". - -```c++ -mCustomParameters["myOwnKey"]; // considering that run and beam type are `default` --> returns `myOwnValue` -mCustomParameters.at("myOwnKey"); // returns `myOwnValue` -mCustomParameters.at("myOwnKey", "default"); // returns `myOwnValue` -mCustomParameters.at("myOwnKey", "default", "default"); // returns `myOwnValue` - -mCustomParameters.at("myOwnKey1", "PHYSICS", "pp"); // returns `myOwnValue1c` -mCustomParameters.at("myOwnKey1", "PHYSICS", "PbPb"); // returns `myOwnValue1d` -mCustomParameters.at("myOwnKey2", "COSMICS"); // returns `myOwnValue2e` - -mCustomParameters.at("myOwnKey1", activity); // result will depend on activity -``` - -### Access values and return default if not found - -The correct way of accessing a parameter and to default to a value if it is not there, is the following: - -```c++ - std::string param = mCustomParameters.atOrDefaultValue("myOwnKey1", "1" /*default value*/, "physics", "pp"); - int casted = std::stoi(param); - - // alternatively - std::string param = mCustomParameters.atOrDefaultValue("myOwnKey1", "1" /*default value*/, activity); // see below how to get the activity -``` - -### Find a value - -Finally the way to search for a value and only act if it is there is the following: - -```c++ - if (auto param2 = mCustomParameters.find("myOwnKey1", "physics", "pp"); param2 != cp.end()) { - int casted = std::stoi(param); - } -``` - -### Retrieve the activity in the modules - -In a task, the `activity` is provided in `startOfActivity`. - -In a Check, it is returned by `getActivity()`. - -In an Aggregator, it is returned by `getActivity()`. - -In a postprocessing task, it is available in the objects manager: `getObjectsManager()->getActivity()` - -## Definition of new arguments - -One can also tell the DPL driver to accept new arguments. This is done using the `customize` method at the top of your workflow definition (usually called "runXXX" in the QC). - -For example, to add two parameters of different types do : - -``` -void customize(std::vector& workflowOptions) -{ - workflowOptions.push_back( - ConfigParamSpec{ "config-path", VariantType::String, "", { "Path to the config file. Overwrite the default paths. Do not use with no-data-sampling." } }); - workflowOptions.push_back( - ConfigParamSpec{ "no-data-sampling", VariantType::Bool, false, { "Skips data sampling, connects directly the task to the producer." } }); -} -``` - -## Reference data - -A reference object is an object from a previous run. It is usually used as a point of comparison. - -### Get a reference plot in a check - -To retrieve a reference plot in your Check, use - -``` - std::shared_ptr CheckInterface::retrieveReference(std::string path, Activity referenceActivity); -``` -* `path` : the path of the object _without the provenance (e.g. `qc`)_ -* `referenceActivity` : the activity of reference (usually the current activity with a different run number) - -If the reference is not found it will return a `nullptr` and the quality is `Null`. - -### Compare to a reference plot - -The check `ReferenceComparatorCheck` in `Common` compares objects to their reference. - -The configuration looks like - -``` - "QcCheck": { - "active": "true", - "className": "o2::quality_control_modules::common::ReferenceComparatorCheck", - "moduleName": "QcCommon", - "policy": "OnAny", - "detectorName": "TST", - "dataSource": [{ - "type": "Task", - "name": "QcTask", - "MOs": ["example"] - }], - "extendedCheckParameters": { - "default": { - "default": { - "referenceRun" : "500", - "moduleName" : "QualityControl", - "comparatorName" : "o2::quality_control_modules::common::ObjectComparatorChi2", - "threshold" : "0.5" - } - }, - "PHYSICS": { - "pp": { - "referenceRun" : "551890" - } - } - } - } -``` - -The check needs the following parameters -* `referenceRun` to specify what is the run of reference and retrieve the reference data. -* `comparatorName` to decide how to compare, see below for their descriptions. -* `threshold` to specifie the value used to discriminate between good and bad matches between the histograms. - -Three comparators are provided: - -1. `o2::quality_control_modules::common::ObjectComparatorDeviation`: comparison based on the average relative deviation between the bins of the current and reference histograms; the `threshold` parameter represent in this case the maximum allowed deviation -2. `o2::quality_control_modules::common::ObjectComparatorChi2`: comparison based on a standard chi2 test between the current and reference histograms; the `threshold` parameter represent in this case the minimum allowed chi2 probability -3. `o2::quality_control_modules::common::ObjectComparatorKolmogorov`: comparison based on a standard Kolmogorov test between the current and reference histograms; the `threshold` parameter represent in this case the minimum allowed Kolmogorov probability - -Note that you can easily specify different reference runs for different run types and beam types. - -The plot is beautified by the addition of a `TPaveText` containing the quality and the reason for the quality. - -### Generate a canvas combining both the current and reference ratio histogram - -The postprocessing task ReferenceComparatorTask draws a given set of plots in comparison with their corresponding references, both as superimposed histograms and as current/reference ratio histograms. -See the details [here](https://github.com/AliceO2Group/QualityControl/blob/master/doc/PostProcessing.md#the-referencecomparatortask-class). - # Miscellaneous diff --git a/doc/Configuration.md b/doc/Configuration.md index 52b90cc41d..9f5cc2a075 100644 --- a/doc/Configuration.md +++ b/doc/Configuration.md @@ -5,13 +5,18 @@ Configuration reference - * [Global configuration structure](./QualityControl/doc/Configuration.md#global-configuration-structure) - * [Common configuration](./QualityControl/doc/Configuration.md#common-configuration) - * [QC Tasks configuration](./QualityControl/doc/Configuration.md#qc-tasks-configuration) - * [QC Checks configuration](./QualityControl/doc/Configuration.md#qc-checks-configuration) - * [QC Aggregators configuration](./QualityControl/doc/Configuration.md#qc-aggregators-configuration) - * [QC Post-processing configuration](./QualityControl/doc/Configuration.md#qc-post-processing-configuration) - * [External tasks configuration](./QualityControl/doc/Configuration.md#external-tasks-configuration) + * [Configuration reference](#configuration-reference) + * [Global configuration structure](#global-configuration-structure) + * [Common configuration](#common-configuration) + * [QC Tasks configuration](#qc-tasks-configuration) + * [QC Checks configuration](#qc-checks-configuration) + * [QC Aggregators configuration](#qc-aggregators-configuration) + * [QC Post-processing configuration](#qc-post-processing-configuration) + * [External tasks configuration](#external-tasks-configuration) + * [Merging multiple configuration files into one](#merging-multiple-configuration-files-into-one) + * [Templating config files](#templating-config-files) + * [Definition and access of simple user-defined task configuration ("taskParameters")](#definition-and-access-of-simple-user-defined-task-configuration-taskparameters) + * [Definition and access of user-defined configuration ("extendedTaskParameters")](#definition-and-access-of-user-defined-configuration-extendedtaskparameters) @@ -369,6 +374,348 @@ Below the external task configuration structure is described. Note that more tha } } ``` + +## Merging multiple configuration files into one + +To merge multiple QC configuration files into one, one can use `jq` in the following way: + +``` +jq -n 'reduce inputs as $s (input; .qc.tasks += ($s.qc.tasks) | .qc.checks += ($s.qc.checks) | .qc.externalTasks += ($s.qc.externalTasks) | .qc.postprocessing += ($s.qc.postprocessing)| .dataSamplingPolicies += ($s.dataSamplingPolicies))' $QC_JSON_GLOBAL $JSON_FILES > $MERGED_JSON_FILENAME +``` + +However, one should pay attention to avoid duplicate task definition keys (e.g. having RawTask twice, each for a different detector), otherwise only one of them would find its way to a merged file. +In such case, one can add the `taskName` parameter in the body of a task configuration structure to use the preferred name and change the root key to a unique id, which shall be used only for the purpose of navigating a configuration file. +If `taskName` does not exist, it is taken from the root key value. +Please remember to update also the references to the task in other actors which refer it (e.g. in Check's data source). + +These two tasks will **not** be merged correctly: + +```json + "RawTask": { + "className": "o2::quality_control_modules::abc::RawTask", + "moduleName": "QcA", + "detectorName": "A", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "raw-a" + } + } +``` + +```json + "RawTask": { + "className": "o2::quality_control_modules::xyz::RawTask", + "moduleName": "QcB", + "detectorName": "B", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "raw-b" + } + } +``` + +The following tasks will be merged correctly: + +```json + "RawTaskA": { + "taskName": "RawTask", + "className": "o2::quality_control_modules::abc::RawTask", + "moduleName": "QcA", + "detectorName": "A", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "raw-a" + } + } +``` + +```json + "RawTaskB": { + "taskName": "RawTask" + "className": "o2::quality_control_modules::xyz::RawTask", + "moduleName": "QcB", + "detectorName": "B", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "raw-b" + } + } +``` + +The same approach can be applied to other actors in the QC framework, like Checks (`checkName`), Aggregators(`aggregatorName`), External Tasks (`taskName`) and Postprocessing Tasks (`taskName`). + +## Templating config files + +> [!WARNING] +> Templating only works when using aliECS, i.e. in production and staging. + +The templating is provided by a template engine called `jinja`. You can use any of its feature. A couple are described below and should satisfy the vast majority of the needs. + +### Preparation + +> [!IMPORTANT] +> Workflows have already been migrated to apricot. This should not be needed anymore. + +To template a config file, modify the corresponding workflow in `ControlWorkflows`. This is needed because we won't use directly `Consul` but instead go through `apricot` to template it. + +1. Replace `consul-json` by `apricot` +2. Replace `consul_endpoint` by `apricot_endpoint` +3. Make sure to have single quotes around the URI + +Example: + +``` +o2-qc --config consul-json://{{ consul_endpoint }}/o2/components/qc/ANY/any/mch-qcmn-epn-full-track-matching --remote -b +``` + +becomes + +``` +o2-qc --config 'apricot://{{ apricot_endpoint }}/o2/components/qc/ANY/any/mch-qcmn-epn-full-track-matching' --remote -b +``` + +Make sure that you are able to run with the new workflow before actually templating. + +### Include a config file + +To include a config file (e.g. named `mch_digits`) add this line : + +``` +{% include "MCH/mch_digits" %} +``` + +The content of the file `mch_digits` is then copied into the config file. Thus make sure that you include all the commas and stuff. + +#### Configuration files organisation + +Once you start including files, you must put the included files inside the corresponding detector subfolder (that have already been created for you). + +Common config files includes are provided in the `COMMON` subfolder. + +### Conditionals + +The `if` looks like + +``` +{% if [condition] %} … {% endif %} +``` + +The condition probably requires some external info, such as the run type or a detectors list. Thus you must pass the info in the ControlWorkflows. + +It could look like this + +``` +o2-qc --config 'apricot://{{ apricot_endpoint }}/o2/components/qc/ANY/any/tpc-pulser-calib-qcmn?run_type={{ run_type }}' ... +``` + +or + +``` +o2-qc --config 'apricot://{{ apricot_endpoint }}/o2/components/qc/ANY/any/mch-qcmn-epn-full-track-matching?detectors={{ detectors }}' ... +``` + +Then use it like this: + +``` +{% if run_type == "PHYSICS" %} +... +{% endif %} +``` + +or like this respectively: + +``` +{% if "mch" in detectors|lower %} +... +{% endif %} +``` + +### Test and debug + +To see how a config file will look like once templated, simply open a browser at this address: `{{apricot_endpoint}}/components/qc/ANY/any/tpc-pulser-calib-qcmn?process=true` +Replace `{{apricot_endpoint}}` by the value you can find in Consul under `o2/runtime/aliecs/vars/apricot_endpoint` (it is different on staging and prod). +_Note that there is no `o2` in the path!!!_ + +### Example + +We are going to create in staging a small example to demonstrate the above. +First create 2 files if they don't exist yet: + +**o2/components/qc/ANY/any/templating_demo** + +``` +{ + "qc": { + "config": {% include "TST/templating_included" %} + {% if run_type == "PHYSICS" %} ,"aggregators": "included" {% endif %} + } +} +``` + +Here we simply include 1 file from a subfolder and add a piece if a certain condition is successful. + +**o2/components/qc/ANY/any/TST/templating_included** + +``` +{ + bookkeeping": { + "url": "alio2-cr1-hv-web01.cern.ch:4001" + } +} +``` + +And now you can try it out: + +``` +http://alio2-cr1-hv-mvs00.cern.ch:32188/components/qc/ANY/any/templating_demo?process=true +``` + +--> the file is included inside the other. + +``` +[http://alio2-cr1-hv-mvs00.cern.ch:32188/components/qc/ANY/any/templating_demo?process=true](http://alio2-cr1-hv-mvs00.cern.ch:32188/components/qc/ANY/any/templating_demo?process=true&run_type=PHYSICS) +``` + +--> the file is included and the condition is true thus we have an extra line. + +## Definition and access of simple user-defined task configuration ("taskParameters") + +The new, extended, way of defining such parameters, not only in Tasks but also in Checks, Aggregators and PP tasks, +is described in the next section. + +A task can access custom parameters declared in the configuration file at `qc.tasks..taskParameters`. They are stored inside an object of type `CustomParameters` named `mCustomParameters`, which is a protected member of `TaskInterface`. + +The syntax is + +```json + "tasks": { + "QcTask": { + "taskParameters": { + "myOwnKey1": "myOwnValue1" + }, +``` + +It is accessed with : `mCustomParameters["myOwnKey"]`. + +## Definition and access of user-defined configuration ("extendedTaskParameters") + +User code, whether it is a Task, a Check, an Aggregator or a PostProcessing task, can access custom parameters declared in the configuration file. +They are stored inside an object of type `CustomParameters` named `mCustomParameters`, which is a protected member of `TaskInterface`. + +The following table gives the path in the config file and the name of the configuration parameter for the various types of user code: + +| User code | Config File item | +|----------------|--------------------------------------------------------| +| Task | `qc.tasks..extendedTaskParameters` | +| Check | `qc.checks..extendedCheckParameters` | +| Aggregator | `qc.aggregators..extendedAggregatorParameters` | +| PostProcessing | `qc.postprocessing..extendedTaskParameters` | + +The new syntax is + +```json + "tasks": { + "QcTask": { + "extendedTaskParameters": { + "default": { + "default": { + "myOwnKey1": "myOwnValue1", + "myOwnKey2": "myOwnValue2", + "myOwnKey3": "myOwnValue3" + } + }, + "PHYSICS": { + "default": { + "myOwnKey1": "myOwnValue1b", + "myOwnKey2": "myOwnValue2b" + }, + "pp": { + "myOwnKey1": "myOwnValue1c" + }, + "PbPb": { + "myOwnKey1": "myOwnValue1d" + } + }, + "COSMICS": { + "myOwnKey1": "myOwnValue1e", + "myOwnKey2": "myOwnValue2e" + } + }, +``` + +It allows to have variations of the parameters depending on the run and beam types. The proper run types can be found here: [ECSDataAdapters.h](https://github.com/AliceO2Group/AliceO2/blob/dev/DataFormats/Parameters/include/DataFormatsParameters/ECSDataAdapters.h#L54). The `default` can be used +to ignore the run or the beam type. +The beam type comes from the parameter `pdp_beam_type` set by ECS and can be one of the following: `pp`, `PbPb`, `pPb`, `pO`, `OO`, `NeNe`, `cosmic`, `technical`. +See `[readout-dataflow](https://github.com/AliceO2Group/ControlWorkflows/blob/master/workflows/readout-dataflow.yaml)` to verify the possible values. + +The values can be accessed in various ways described in the following sub-sections. + +### Access optional values with or without activity + +The value for the key, runType and beamType is returned if found, or an empty value otherwise. +However, before returning an empty value we try to substitute the runType and the beamType with "default". + +```c++ +// returns an Optional if it finds the key `myOwnKey` for the runType and beamType of the provided activity, +// or if it can find the key with the runType or beamType substituted with "default". +auto param = mCustomParameters.atOptional("myOwnKey1", activity); // activity is "PHYSICS", "PbPb" , returns "myOwnValue1d" +// same but passing directly the run and beam types +auto param = mCustomParameters.atOptional("myOwnKey1", "PHYSICS", "PbPb"); // returns "myOwnValue1d" +// or with only the run type +auto param = mCustomParameters.atOptional("myOwnKey1", "PHYSICS"); // returns "myOwnValue1b" +``` + +### Access values directly specifying the run and beam type or an activity + +The value for the key, runType and beamType is returned if found, or an exception is thrown otherwise.. +However, before throwing we try to substitute the runType and the beamType with "default". + +```c++ +mCustomParameters["myOwnKey"]; // considering that run and beam type are `default` --> returns `myOwnValue` +mCustomParameters.at("myOwnKey"); // returns `myOwnValue` +mCustomParameters.at("myOwnKey", "default"); // returns `myOwnValue` +mCustomParameters.at("myOwnKey", "default", "default"); // returns `myOwnValue` + +mCustomParameters.at("myOwnKey1", "PHYSICS", "pp"); // returns `myOwnValue1c` +mCustomParameters.at("myOwnKey1", "PHYSICS", "PbPb"); // returns `myOwnValue1d` +mCustomParameters.at("myOwnKey2", "COSMICS"); // returns `myOwnValue2e` + +mCustomParameters.at("myOwnKey1", activity); // result will depend on activity +``` + +### Access values and return default if not found + +The correct way of accessing a parameter and to default to a value if it is not there, is the following: + +```c++ + std::string param = mCustomParameters.atOrDefaultValue("myOwnKey1", "1" /*default value*/, "physics", "pp"); + int casted = std::stoi(param); + + // alternatively + std::string param = mCustomParameters.atOrDefaultValue("myOwnKey1", "1" /*default value*/, activity); // see below how to get the activity +``` + +### Find a value + +Finally the way to search for a value and only act if it is there is the following: + +```c++ + if (auto param2 = mCustomParameters.find("myOwnKey1", "physics", "pp"); param2 != cp.end()) { + int casted = std::stoi(param); + } +``` + +### Retrieve the activity in the modules + +In a task, the `activity` is provided in `startOfActivity`. + +In a Check, it is returned by `getActivity()`. + +In an Aggregator, it is returned by `getActivity()`. + +In a postprocessing task, it is available in the objects manager: `getObjectsManager()->getActivity()` + + --- -[← Go back to Advanced](Advanced.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to Frequently Asked Questions →](FAQ.md) +[← Go back to Framework](Framework.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to Frequently Asked Questions →](FAQ.md) diff --git a/doc/FLPsuite.md b/doc/FLPsuite.md new file mode 100644 index 0000000000..27161edd90 --- /dev/null +++ b/doc/FLPsuite.md @@ -0,0 +1,208 @@ + +FLP Suite +--- + + + + + * [Developing QC modules on a machine with FLP suite](#developing-qc-modules-on-a-machine-with-flp-suite) + * [Switch detector in the workflow readout-dataflow](#switch-detector-in-the-workflow-readout-dataflow) + * [Get all the task output to the infologger](#get-all-the-task-output-to-the-infologger) + * [Using a different config file with the general QC](#using-a-different-config-file-with-the-general-qc) + * [Enable the repo cleaner](#enable-the-repo-cleaner) + * [Definition of new arguments](#definition-of-new-arguments) + * [Reference data](#reference-data) + + +# FLP Suite + +The QC is part of the FLP Suite. The Suite is installed on FLPs through RPMs and is configured with ansible. As a consequence a few things are different in this context compared to a pure development setup. + +## Developing QC modules on a machine with FLP suite + +Development RPMs are available on the FLPs. Start by installing them, then compile QC and finally tell aliECS to use it. + +**Installation** + +As root do: + +``` +yum install o2-QualityControl-devel git -y +``` + +**Compilation** + +As user `flp` do: + +``` +git clone https://github.com/AliceO2Group/QualityControl.git +cd QualityControl +git checkout # use the release included in the installed FLP suite +mkdir build +cd build +mkdir /tmp/installdir +cmake -DCMAKE_INSTALL_PREFIX=/tmp/installdir -G Ninja -DCLANG_EXECUTABLE=/opt/o2/bin-safe/clang -DCMAKE_BUILD_TYPE=RelWithDebugInfo .. +ninja -j16 install +``` + +_**Compilation on top of a local O2**_ + +If you want to build also O2 locally do + +``` +# O2 +git clone https://github.com/AliceO2Group/AliceO2.git +cd AliceO2 +git checkout # use the release included in the installed FLP suite +mkdir build +cd build +cmake -DCMAKE_INSTALL_PREFIX=/tmp/installdir -G Ninja -DCLANG_EXECUTABLE=/opt/o2/bin-safe/clang -DCMAKE_BUILD_TYPE=RelWithDebugInfo .. +ninja -j8 install + +# QC +git clone https://github.com/AliceO2Group/QualityControl.git +cd QualityControl +git checkout # use the release included in the installed FLP suite +mkdir build +cd build +cmake -DCMAKE_INSTALL_PREFIX=/tmp/installdir -G Ninja -DCLANG_EXECUTABLE=/opt/o2/bin-safe/clang -DCMAKE_BUILD_TYPE=RelWithDebugInfo -DO2_ROOT=/tmp/installdir .. +ninja -j8 install +``` + +_**Important step in case several nodes are involved**_ + +In case the workflows will span over several FLPs and/or QC machines, one should `scp` the `installdir` to the other machines in the same directory. + +**Use it in aliECS** + +In the aliECS gui, in the panel "Advanced Configuration", et an extra variable `extra_env_vars` and set it to + +``` +PATH=/tmp/installdir/bin/:$PATH; LD_LIBRARY_PATH=/tmp/installdir/lib/:/tmp/installdir/lib64/:$LD_LIBRARY_PATH; QUALITYCONTROL_ROOT=/tmp/installdir/; echo +``` + +Replace `/tmp/installdir` with your own path. Make sure that the directory is anyway readable and traversable by users `flp` and `qc` + +## Switch detector in the workflow _readout-dataflow_ + +The workflow readout-dataflow works by default with the detector code TST. To run with another detector (e.g. EMC) do: + +2. Replace all instances of `TST` in the QC config file in consul with the one of the detector (e.g. `EMC`). +2. Set the variable `detector` in aliECS to the detector (e.g. `EMC`). + +## Get all the task output to the infologger + +Set the variable log_task_output=all + +## Using a different config file with the general QC + +One can set the `QC URI` to a different config file that is used by the general QC when enabled. However, this is not the recommended way. One must make sure that the name of the task and the check are left untouched and that they are both enabled. + +## Enable the repo cleaner + +If the CCDB used in an FLP setup is the local one, the repo cleaner might be necessary as to avoid filling up the disk of the machine. + +By defaults there is a _disabled_ cron job : + +```shell +*/10 * * * * /opt/o2/bin/o2-qc-repo-cleaner --config /etc/flp.d/ccdb-sql/repocleaner.yaml --dry-run > /dev/null 2>> /tmp/cron-errors.txt +``` + +1. copy the config file /etc/flp.d/ccdb-sql/repocleaner.yaml +2. modify the config file to suit your needs +3. run by hand the repo-cleaner to check that the config file is ok +3. update the cron job to use the modified config file +4. uncomment the cron job + +## Definition of new arguments + +One can also tell the DPL driver to accept new arguments. This is done using the `customize` method at the top of your workflow definition (usually called "runXXX" in the QC). + +For example, to add two parameters of different types do : + +``` +void customize(std::vector& workflowOptions) +{ + workflowOptions.push_back( + ConfigParamSpec{ "config-path", VariantType::String, "", { "Path to the config file. Overwrite the default paths. Do not use with no-data-sampling." } }); + workflowOptions.push_back( + ConfigParamSpec{ "no-data-sampling", VariantType::Bool, false, { "Skips data sampling, connects directly the task to the producer." } }); +} +``` + +## Reference data + +A reference object is an object from a previous run. It is usually used as a point of comparison. + +### Get a reference plot in a check + +To retrieve a reference plot in your Check, use + +``` + std::shared_ptr CheckInterface::retrieveReference(std::string path, Activity referenceActivity); +``` +* `path` : the path of the object _without the provenance (e.g. `qc`)_ +* `referenceActivity` : the activity of reference (usually the current activity with a different run number) + +If the reference is not found it will return a `nullptr` and the quality is `Null`. + +### Compare to a reference plot + +The check `ReferenceComparatorCheck` in `Common` compares objects to their reference. + +The configuration looks like + +``` + "QcCheck": { + "active": "true", + "className": "o2::quality_control_modules::common::ReferenceComparatorCheck", + "moduleName": "QcCommon", + "policy": "OnAny", + "detectorName": "TST", + "dataSource": [{ + "type": "Task", + "name": "QcTask", + "MOs": ["example"] + }], + "extendedCheckParameters": { + "default": { + "default": { + "referenceRun" : "500", + "moduleName" : "QualityControl", + "comparatorName" : "o2::quality_control_modules::common::ObjectComparatorChi2", + "threshold" : "0.5" + } + }, + "PHYSICS": { + "pp": { + "referenceRun" : "551890" + } + } + } + } +``` + +The check needs the following parameters +* `referenceRun` to specify what is the run of reference and retrieve the reference data. +* `comparatorName` to decide how to compare, see below for their descriptions. +* `threshold` to specifie the value used to discriminate between good and bad matches between the histograms. + +Three comparators are provided: + +1. `o2::quality_control_modules::common::ObjectComparatorDeviation`: comparison based on the average relative deviation between the bins of the current and reference histograms; the `threshold` parameter represent in this case the maximum allowed deviation +2. `o2::quality_control_modules::common::ObjectComparatorChi2`: comparison based on a standard chi2 test between the current and reference histograms; the `threshold` parameter represent in this case the minimum allowed chi2 probability +3. `o2::quality_control_modules::common::ObjectComparatorKolmogorov`: comparison based on a standard Kolmogorov test between the current and reference histograms; the `threshold` parameter represent in this case the minimum allowed Kolmogorov probability + +Note that you can easily specify different reference runs for different run types and beam types. + +The plot is beautified by the addition of a `TPaveText` containing the quality and the reason for the quality. + +### Generate a canvas combining both the current and reference ratio histogram + +The postprocessing task ReferenceComparatorTask draws a given set of plots in comparison with their corresponding references, both as superimposed histograms and as current/reference ratio histograms. +See the details [here](https://github.com/AliceO2Group/QualityControl/blob/master/doc/PostProcessing.md#the-referencecomparatortask-class). + + +--- + +[← Go back to Post-processing](PostProcessing.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to Configuration →](Configuration.md) diff --git a/doc/Framework.md b/doc/Framework.md new file mode 100644 index 0000000000..6421f0ef4a --- /dev/null +++ b/doc/Framework.md @@ -0,0 +1,733 @@ + +Framework +--- + + + + +* [Plugging the QC to an existing DPL workflow](#plugging-the-qc-to-an-existing-dpl-workflow) +* [Production of QC objects outside this framework](#production-of-qc-objects-outside-this-framework) +* [Multi-node setups](#multi-node-setups) +* [Batch processing](#batch-processing) +* [Moving window](#moving-window) +* [Monitor cycles](#monitor-cycles) +* [Writing a DPL data producer](#writing-a-dpl-data-producer) +* [Custom merging](#custom-merging) +* [Critical, resilient and non-critical tasks](#critical-resilient-and-non-critical-tasks) +* [QC with DPL Analysis](#qc-with-dpl-analysis) + * [Uploading objects to QCDB](#uploading-objects-to-qcdb) +* [Propagating Check results to RCT in Bookkeeping](#propagating-check-results-to-rct-in-bookkeeping) + * [Conversion details](#conversion-details) +* [Solving performance issues](#solving-performance-issues) + * [Dispatcher](#dispatcher) + * [QC Tasks](#qc-tasks-1) + * [Mergers](#mergers) +* [Understanding and reducing memory footprint](#understanding-and-reducing-memory-footprint) + + +## Plugging the QC to an existing DPL workflow + +Your existing DPL workflow can simply be considered a publisher. Therefore, replace `o2-qc-run-producer` with your own workflow. + +For example, if TPC wants to monitor the output `{"TPC", "CLUSTERS"}` of the workflow `o2-qc-run-tpcpid`, modify the config file to point to the correct data and do : + +``` +o2-qc-run-tpcpid | o2-qc --config json://${QUALITYCONTROL_ROOT}/etc/tpcQCPID.json +``` + +## Production of QC objects outside this framework + +QC objects (e.g. histograms) are typically produced in a QC task. +This is however not the only way. Some processing tasks such as the calibration +might have already processed the data and produced histograms that should be +monitored. Instead of re-processing and doing twice the work, one can simply +push this QC object to the QC framework where it will be checked and stored. + +### Configuration + +Let be a device in the main data flow that produces a histogram on a channel defined as `TST/HISTO/0`. To get this histogram in the QC and check it, add to the configuration file an "external device": + +```yaml + "externalTasks": { + "External-1": { + "active": "true", + "query": "External-1:TST/HISTO/0", "": "Query specifying where the objects to be checked and stored are coming from. Use the task name as binding. The origin (e.g. TST) is used as detector name for the objects." + } + }, + "checks": { +``` + +The "query" syntax is the same as the one used in the DPL and in the Dispatcher. It must match the output of another device, whether it is in the same workflow or in a piped one. +The `binding` (first part, before the colon) is used in the path of the stored objects and thus we encourage to use the task name to avoid confusion. Moreover, the `origin` (first element after the colon) is used as detectorName. + +### Example 1: basic + +As a basic example, we are going to produce histograms with the HistoProducer and collect them with the QC. The configuration is in [basic-external-histo.json](https://github.com/AliceO2Group/QualityControl/blob/master/Framework/basic-external-histo.json). An external task is defined and named "External-1" (see subsection above). It is then used in the Check QCCheck : + +```yaml + "QcCheck": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonCheck", + "moduleName": "QcSkeleton", + "policy": "OnAny", + "detectorName": "TST", + "dataSource": [{ + "type": "ExternalTask", + "name": "External-1", + "MOs": ["hello"] + }] + } +``` + +When using this feature, make sure that the name of the MO in the Check definition matches the name of the object you are sending from the external device. + +To run it, do: + +```yaml +o2-qc-run-histo-producer | o2-qc --config json://${QUALITYCONTROL_ROOT}/etc/basic-external-histo.json +``` + +The object is visible in the QCG or the CCDB at `qc/TST/MO/External-1/hello_0`. In general we publish the objects of an external device at `qc//MO//object`. + +The check results are stored at `qc//QO//object`. + +### Example 2: advanced + +This second, more advanced, example mixes QC tasks and external tasks. It is defined in [advanced-external-histo.json](https://github.com/AliceO2Group/QualityControl/blob/master/Framework/advanced-external-histo.json). It is represented here: + +![alt text](images/Advanced-external.png) + +First, it runs 1 QC task (QC-TASK-RUNNER-QcTask) getting data from a data producer (bottom boxes, typical QC worfklow). + +On top we see 3 histogram producers. `histoProducer-2` is not part of the QC, it is not an external device defined in the configuration file. The two other histogram producers are configured as external devices in the configuration file. + +`histoProducer-0` produces an object that is used in a check (`QcCheck-External-1`). `histoProducer-1` objects are not used in any check but we generate one automatically to take care of the storage in the database. + +To run it, do: + +```yaml +o2-qc-run-producer | o2-qc-run-histo-producer --producers 3 --histograms 3 | o2-qc --config json://${QUALITYCONTROL_ROOT}/etc/advanced-external-histo.json +``` + +### Limitations + +1. Objects sent by the external device must be either a TObject or a TObjArray. In the former case, the object will be sent to the checker encapsulated in a MonitorObject. In the latter case, each TObject of the TObjArray is encapsulated in a MonitorObject and is sent to the checker. + +## Multi-node setups + +During the data-taking Quality Control runs on a distributed computing system. Some QC Tasks are +executed on dedicated QC servers, while others run on FLPs and EPNs. In the first case, messages +coming from Data Sampling should reach QC servers where they are processed. In the latter case, +locally produced Monitor Objects should be merged on QC servers and then have Checks run on them. +By **remote QC tasks** we mean those which run on QC servers (**remote machines**), while **local QC Tasks** +run on FLPs and EPNs (**local machines**). + +Setting up a multinode setup to run standalone or with AliECS requires different amount of parameters, +as some of them are overwritten by AliECS anyway. Such parameters are marked accordingly. Please note +that for now we support cases with one or more local machines, but just only one remote machine. + +In our example, we assume having two local processing nodes (`localnode1`, `localnode2`) and one +QC node (`qcnode`). There are two types of QC Tasks declared: +* `MultiNodeLocal` which are executed on the local nodes and their results are merged and checked + on the QC server. +* `MultiNodeRemote` which runs on the QC server, receiving a small percent of data from + `localnode2` only. Mergers are not needed in this case, but there is a process running Checks against + Monitor Objects generated by this Task. + +We use the `SkeletonTask` class for both, but any Task can be used of course. Should a Task be local, +all its `MonitorObject`s need to be mergeable - they should be one of the mergeable ROOT types (histograms, TTrees) +or inherit [MergeInterface](https://github.com/AliceO2Group/AliceO2/blob/dev/Utilities/Mergers/include/Mergers/MergeInterface.h). + +These are the steps to follow to get a multinode setup: + +1. Prepare a configuration file. + +In this example we will use the `Framework/multiNode.json` config file. A config file should look +almost like the usual one, but with a few additional parameters. In case of a local task, these parameters should be +added: + +```json + "tasks": { + "MultiNodeLocal": { + "active": "true", + ... + "location": "local", + "localMachines": [ + "localnode1", + "localnode2" + ], + "remoteMachine": "qcnode", "":"not needed with FLP+QC, needed with EPN+QC", + "remotePort": "30132", "":"not needed with FLP+QC, needed with EPN+QC", + "localControl": "aliecs", "":"if absent, aliecs is default", + "mergingMode": "delta", "":"if absent, delta is default", + "mergersPerLayer": ["3", "1"], "":"if absent, one Merger is used" + } + }, +``` + +List the local processing machines in the `localMachines` array. `remoteMachine` should contain the host name which +will serve as a QC server and `remotePort` should be a port number on which Mergers will wait for upcoming MOs. Make +sure it is not used by other service. If different QC Tasks are run in parallel, use separate ports for each. The +`localControl` parameter allows to properly configure QC with respect to the control software it is run with. It can +be either `aliecs` (on FLPs) or `odc` (EPNs). It has no influence when running the software by hand. + +One also may choose the merging mode - `delta` is the default and recommended (tasks are reset after each cycle, so they +send only updates), but if it is not feasible, Mergers may expect `entire` objects - tasks are not reset, they +always send entire objects and the latest versions are combined in Mergers. +With the `delta` mode, one can cheat by specifying just one local machine name and using only that one during execution. +This is not possible with `entire` mode, because then Mergers need identifiable data sources to merge objects correctly. +If one merger process is not enough to sustain the input data throughput, one may define multiple Merger layers with +`mergersPerLayer` option. + +In case of a remote task, choosing `"remote"` option for the `"location"` parameter is needed. In standalone setups +and those controlled by ODC, one should also specify the `"remoteMachine"`, so sampled data reaches the right node. +Also, `"localControl"` should be specified to generate the correct AliECS workflow template. + +```json + "tasks": { + ... + "MultiNodeRemote": { + "active": "true", + ... + "dataSource": { + "type": "dataSamplingPolicy", + "name": "rnd-little" + }, + "taskParameters": {}, + "location": "remote", + "remoteMachine": "qcnode", "":"not needed with FLP+QC, needed with EPN+QC", + "localControl": "aliecs", "":"aliecs is default, not needed with FLP+QC, needed with EPN+QC" + } + } +``` + +In case the task is running remotely, data should be sampled. The minimal-effort approach requires adding a port number +(see the example below). Use separate ports for each Data Sampling Policy. If the same configuration file will be used +on many nodes, but only some of them should apply a given sampling policy, one should also specify the list of +machines to match (or generalized aliases, e.g. "flp", "epn"). + +```json +{ + "dataSamplingPolicies": [ + ... + { + "id": "rnd-little", + "active": "true", + "machines": [ "","only needed when the policy should run on a subgroup of nodes", + "localnode2" + ], + "port": "30333", "":"compulsory on standalone and ODC setups (EPN), not needed for FLPs", + ... + } + ] +} +``` + +By default, the channel is bound on the QC Task side. If this is not what you need, add `"bindLocation" : "local"` in +the policy configuration (`"remote"` is the default value) and make sure to use valid host names. + +2. Make sure that the firewalls are properly configured. If your machines block incoming/outgoing connections by + default, you can add these rules to the firewall (run as sudo). Consider enabling only concrete ports or a small + range of those. + +``` +# localnode1 and localnode2 : +iptables -I INPUT -p tcp -m conntrack --ctstate NEW,ESTABLISHED -s qcnode -j ACCEPT +iptables -I OUTPUT -p tcp -m conntrack --ctstate NEW,ESTABLISHED -d qcnode -j ACCEPT +# qcnode: +iptables -I INPUT -p tcp -m conntrack --ctstate NEW,ESTABLISHED -s localnode1 -j ACCEPT +iptables -I OUTPUT -p tcp -m conntrack --ctstate NEW,ESTABLISHED -d localnode1 -j ACCEPT +iptables -I INPUT -p tcp -m conntrack --ctstate NEW,ESTABLISHED -s localnode2 -j ACCEPT +iptables -I OUTPUT -p tcp -m conntrack --ctstate NEW,ESTABLISHED -d localnode2 -j ACCEPT +``` + +If your network is isolated, you might consider disabling the firewall as an alternative. Be wary of the security risks. + +``` +systemctl stop firewalld # to disable until reboot +systemctl disable firewalld # to disable permanently +``` + +3. Install the same version of the QC software on each of these nodes. We cannot guarantee that different QC versions will talk to each other without problems. Also, make sure the configuration file that you will use is the same everywhere. + +4. Run each part of the workflow. In this example `o2-qc-run-producer` represents any DPL workflow, here it is just a process which produces some random data. + The `--host` argument is matched against the `machines` lists in the configuration files. + +``` +# On localnode1: +o2-qc-run-producer | o2-qc --config json:/${QUALITYCONTROL_ROOT}/etc/multiNode.json --local --host localnode1 -b +# On localnode2: +o2-qc-run-producer | o2-qc --config json:/${QUALITYCONTROL_ROOT}/etc/multiNode.json --local --host localnode2 -b +# On qcnode: +o2-qc --config json:/${QUALITYCONTROL_ROOT}/etc/multiNode.json --remote +``` + +If there are no problems, on QCG you should see the `example` histogram updated under the paths `qc/TST/MO/MultiNodeLocal` +and `qc/TST/MO/MultiNodeRemote`, and corresponding Checks under the path `qc/TST/QO/`. + +When using AliECS, one has to generate workflow templates and upload them to the corresponding repository. Please +contact the QC or AliECS developers to receive assistance or instructions on how to do that. + +## Batch processing + +In certain cases merging results of parallel QC Tasks cannot be performed in form of message passing. +An example of this are the simulation workflows, which exchange data between processing stages via files +and produce (and process) consecutive TimeFrames in different directories in parallel. +Then, one can run QC Tasks on incomplete data and save the results to a file. +If the file already exists, the new objects will be merged with those obtained so far. +At the end, one can run the rest of processing chain (Checks, Aggregators) on the complete objects. + +Here is a simple example: + +```bash +# Remove any existing results +rm results.root +# Run the Tasks 3 times, merge results into the file. +o2-qc-run-producer --message-amount 100 | o2-qc --config json:/${QUALITYCONTROL_ROOT}/etc/basic.json --local-batch results.root +o2-qc-run-producer --message-amount 100 | o2-qc --config json:/${QUALITYCONTROL_ROOT}/etc/basic.json --local-batch results.root +o2-qc-run-producer --message-amount 100 | o2-qc --config json:/${QUALITYCONTROL_ROOT}/etc/basic.json --local-batch results.root +# Run Checks and Aggregators, publish results to QCDB +o2-qc --config json:/${QUALITYCONTROL_ROOT}/etc/basic.json --remote-batch results.root +``` + +Please note, that the local batch QC workflow should not work on the same file at the same time. +A semaphore mechanism is required if there is a risk they might be executed in parallel. + +The file is organized into directories named after 3-letter detector codes and sub-directories representing Monitor Object Collections for specific tasks. +To browse the file, one needs the associated Quality Control environment loaded, since it contains QC-specific data structures. +It is worth remembering, that this file is considered as intermediate storage, thus Monitor Object do not have Checks applied and cannot be considered the final results. +The quick and easy way to inspect the contents of the file is to load a recent environment (e.g. on lxplus) and open it with ROOT's `TBrowser`: + +```shell +alienv enter O2PDPSuite/nightly-20221219-1 +root +TBrowser t; // a browser window will pop-up +``` + +...or by browsing the file manually: + +```shell +alienv enter O2PDPSuite/nightly-20221219-1 +root +root [0] auto f = new TFile("QC_fullrun.root") +(TFile *) @0x7ffe84833dc8 +root [1] f->ls() +TFile** QC_fullrun.root + TFile* QC_fullrun.root + KEY: TDirectoryFile CPV;1 CPV + KEY: TDirectoryFile EMC;1 EMC + KEY: TDirectoryFile FDD;1 FDD + KEY: TDirectoryFile FT0;1 FT0 + KEY: TDirectoryFile FV0;1 FV0 + KEY: TDirectoryFile GLO;1 GLO + KEY: TDirectoryFile ITS;1 ITS +... +root [2] f->cd("GLO") +(bool) true +root [3] f->ls() +TFile** QC_fullrun.root + TFile* QC_fullrun.root + TDirectoryFile* GLO GLO + KEY: o2::quality_control::core::MonitorObjectCollection MTCITSTPC;1 + KEY: o2::quality_control::core::MonitorObjectCollection Vertexing;1 + KEY: TDirectoryFile CPV;1 CPV +... +root [4] auto vtx = dynamic_cast(f->Get("GLO/Vertexing")) +(o2::quality_control::core::MonitorObjectCollection *) @0x7ffe84833dc8 +root [5] auto vtx_x = dynamic_cast(vtx->FindObject("vertex_X")) +(o2::quality_control::core::MonitorObject *) @0x7ffe84833dc8 +root [6] vtx_x->getObject()->ClassName() +(const char *) "TH1F" +``` + +To merge several incomplete QC files, one can use the `o2-qc-file-merger` executable. +It takes a list of input files, which may or may not reside on alien, and produces a merged file. +One can select whether the executable should fail upon any error or continue for as long as possible. +Please see its `--help` output for usage details. + +## Moving window + +### Moving window for all plots generated by a task + +By default QC Tasks are never reset, thus the MOs they produce contain data from the full run. +However, if objects should have a shorter validity range, one may add the following options to QC Task configuration: + +```json + "MovingWindowTaskA": { + ... + "resetAfterCycles": "10", + } +``` + +In the case above the QC Task will have the `TaskInterface::reset()` method invoked each 10 cycles. +Thus, all the plots generated by this task will by affected. + +If the QC Task runs in parallel on many nodes and its results are merged, the effects will be different +depending on the chosen merging mode: +* If `"delta"` mode is used, the Merger in the last layer will implement the moving window, while the QC Tasks will + still reset after each cycle. Please note, that QC Tasks will fall out of sync during data acquisition, so the moving + window might contain slightly misaligned data time ranges coming from different sources. Also, due to fluctuations of + the data transfer, objects coming from different sources might appear more frequently than others. Thus, one might + notice higher occupancy on stave A one time, but the next object might contain less than average data for the same stave. +* In the `"entire"` mode, QC Tasks will reset MOs, while Mergers will use the latest available object version from each + Task. Please note that if one of the Tasks dies, an old version of MO will be still used over and over. Thus, `"delta"` + mode is advised in most use cases. + +In setups with Mergers one may also extend the Mergers cycle duration, which can help to even out any data fluctuations: + +```json + "MovingWindowTaskB": { + ... + "cycleDurationSeconds" : "60", + "mergingMode" : "delta", + "mergerCycleMultiplier": "10", "": "multiplies cycleDurationSeconds in Mergers", + "resetAfterCycles": "1", "": "it could be still larger than 1" + } + ``` + +In the presented case, the Merger will publish one set of complete MOs per 10 minutes, which should contain all deltas +received during this last period. Since the QC Tasks cycle is 10 times shorter, the occupancy fluctuations should be +less apparent. Please also note, that using this parameter in the `"entire"` merging mode does not make much sense, +since Mergers would use every 10th incomplete MO version when merging. + +### Moving windows of selected plots only + +The following applies to synchronous setups which use Mergers in the delta mode and all asynchronous setups. +One can obtain objects containing data from one cycle alongside the ones covering the whole run. +These are saved in QCDB in the task subdirectory `mw` and also can be requested by Checks. +To specify which objects should get a moving window variant, add a `"movingWindows"` list to the task configuration: + +```json + "MyTask": { + ... + "cycleDurationSeconds" : "60", + "mergingMode" : "delta", + "movingWindows" : [ "plotA", "plotB" ] + } +``` + +To request these objects in a Check, use `TaskMovingWindow` data source, as in the example: + +```json + "QcCheckMW": { + "dataSource": [{ + "type": "TaskMovingWindow", + "name": "MyTask", + "MOs": ["plotA"], "": "MOs can be omitted if all moving windows of a task are requested" + }] + } +``` + +It is possible to request both the integrated and single cycle plots by the same Check. + +To test it in a small setup, one can run `o2-qc` with `--full-chain` flag, which creates a complete workflow with a Merger for **local** QC tasks, even though it runs just one instance of them. +Please remember to use `"location" : "local"` in such case. + +In asynchronous QC, the moving window plots will appear in the intermediate QC file in the directory `mw` and will be uploaded to QCDB to `/mw`. +When testing, please make sure to let DPL know that it has to run in Grid mode, so that QC can compute object validity based on timestamps in the data: + +``` +export O2_DPL_DEPLOYMENT_MODE=Grid && o2-qc --local-batch QC.root ... +``` + +## Monitor cycles + +The QC tasks monitor and process data continuously during a so-called "monitor cycle". At the end of such a cycle they publish the QC objects that will then continue their way in the QC data flow. + +A monitor cycle lasts typically between **1 and 5 minutes**, some reaching 10 minutes but never less than 1 minute for performance reasons. +It is defined in the config file this way: + +``` + "tasks": { + "dataSizeTask": { + "cycleDurationSeconds": "60", + ... +``` + +It is possible to specify various durations for different period of times. It is particularly useful to have shorter cycles at the beginning of the run and longer afterwards: + +``` + "tasks": { + "dataSizeTask": { + "cycleDurations": [ + {"cycleDurationSeconds": 60, "validitySeconds": 300}, + {"cycleDurationSeconds": 180, "validitySeconds": 600}, + {"cycleDurationSeconds": 300, "validitySeconds": 1} + ], + ... +``` + +In this example, a cycle of 60 seconds is used for the first 5 minutes (300 seconds), then a cycle of 3 minutes (180 seconds) between 5 minutes and 10 minutes after SOR, and finally a cycle of 5 minutes for the rest of the run. The last `validitySeconds` is not used and is just applied for the rest of the run. + +## Writing a DPL data producer + +For your convenience, and although it does not lie within the QC scope, we would like to document how to write a simple data producer in the DPL. The DPL documentation can be found [here](https://github.com/AliceO2Group/AliceO2/blob/dev/Framework/Core/README.md) and for questions please head to the [forum](https://alice-talk.web.cern.ch/). + +As an example we take the `DataProducerExample` that you can find in the QC repository. It is produces a number. By default it will be 1s but one can specify with the parameter `my-param` a different number. It is made of 3 files : + +* [runDataProducerExample.cxx](../Framework/src/runDataProducerExample.cxx) : + This is an executable with a basic data producer in the Data Processing Layer. + There are 2 important functions here : + * `customize(...)` to add parameters to the executable. Note that it must be written before the includes for the dataProcessing. + * `defineDataProcessing(...)` to define the workflow to be ran, in our case the device(s) publishing the number. +* [DataProducerExample.h](../Framework/include/QualityControl/DataProducerExample.h) : + The key elements are : + 1. The include `#include ` + 2. The function `getDataProducerExampleSpec(...)` which must return a `DataProcessorSpec` i.e. the description of a device (name, inputs, outputs, algorithm) + 3. The function `getDataProducerExampleAlgorithm` which must return an `AlgorithmSpec` i.e. the actual algorithm that produces the data. +* [DataProducerExample.cxx](../Framework/src/DataProducerExample.cxx) : + This is just the implementation of the header described just above. You will probably want to modify `getDataProducerExampleSpec` and the inner-most block of `getDataProducerExampleAlgorithm`. You might be taken aback by the look of this function, if you don't know what a _lambda_ is just ignore it and write your code inside the accolades. + +You will probably write it in your detector's O2 directory rather than in the QC repository. + +## Custom merging + +When needed, one may define their own algorithm to merge a Monitor Object. +To do so, inherit the [MergeInterface](https://github.com/AliceO2Group/AliceO2/blob/dev/Utilities/Mergers/include/Mergers/MergeInterface.h) class and override the corresponding methods. +Please pay special attention to delete all the allocated resources in the destructor to avoid any memory leaks. +Feel free to consult the existing usage examples among other modules in the QC repository. + +Once a custom class is implemented, one should let QCG know how to display it correctly, which is explained in the subsection [Display a non-standard ROOT object in QCG](#display-a-non-standard-root-object-in-qcg). + +## Critical, resilient and non-critical tasks + +DPL devices can be marked as expendable, resilient or critical. Expendable tasks can die without affecting the run. +Resilient tasks can survive having one or all their inputs coming from an expendable task but they will stop the system if they themselves die. +Critical tasks (default) will stop the system if they die and will not accept input from expendable tasks. + +In QC we use these `labels`. + +### QC tasks + +In QC, one can mark a task as critical or non-critical: + +```json + "tasks": { + "QcTask": { + "active": "true", + "critical": "false", "": "if false the task is allowed to die without stopping the workflow, default: true", +``` + +By default they are `critical` meaning that their failure will stop the run. +If they are not critical, they will be `expendable` and will not stop the run if they die. + +### Auto-generated proxies + +They adopt the criticality of the task they are proxying. + +### QC mergers + +Mergers are `resilient`. + +### QC check runners + +CheckRunners are `resilient`. + +### QC aggregators + +Aggregators are `resilient`. + +### QC post-processing tasks + +Post-processing tasks can be marked as critical or non-critical: + +```json + "postprocessing": { + "ExamplePostprocessing": { + "active": "true", + "critical": "false", "": "if false the task is allowed to die without stopping the workflow, default: true", +``` + +By default, they are critical meaning that their failure will stop the run. +If they are not critical, they will be `expendable` and will not stop the run if they die. + +## QC with DPL Analysis + +### 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 by default the executable will ignore the directory structure in the input file and upload all objects to one directory. +If you need the directory structure preserved, add the argument `--preserve-directories`. + +## Propagating Check results to RCT in Bookkeeping + +The framework allows to propagate Quality Objects (QOs) produced by Checks and Aggregators to RCT in Bookkeeping. +The synchronisation is done once, at the end of workflow runtime, i.e. at the End of Run or in the last stage of QC merging on Grid. +Propagation can be enabled by adding the following key-value pair to Check/Aggregator configuration: + +```json + "exportToBookkeeping": "true" +``` + +Using it for Aggregators is discouraged, as the information on which exact Check failed is lost or at least obfuscated. + +Also, make sure that the configuration file includes the Bookkeeping URL. + +Check results are converted into Flags, which are documented in [O2/DataFormats/QualityControl](https://github.com/AliceO2Group/AliceO2/tree/dev/DataFormats/QualityControl). +Information about the object validity is preserved, which allows for time-based flagging of good/bad data. + +### Conversion details + +Below we describe some details of how the conversion is done. +Good QOs are marked with green, Medium QOs are marked with orange and Bad QOs are marked with red. +Null QOs are marked with purple. + +* **Good QOs with no Flags associated are not converted to any Flags.** + According to the preliminary design for Data Tagging, "bad" Flags always win, thus there is no need for explicit "good" Flags. + It also implies that there is no need to explicitly add Good Flag to Good Quality. + +![](images/qo_flag_conversion_01.svg) + +* **Bad and Medium QOs with no Flags are converted to Flag 14 (Unknown).** + This means that Medium Quality data is by default bad for Analysis. + +![](images/qo_flag_conversion_02.svg) + +* **Null QOs with no Flags are converted to Flag 1 (Unknown Quality).** + +![](images/qo_flag_conversion_03.svg) + +* **All QOs with Flags are converted to Flags, while the Quality is ignored.** + As a consequence, one can customize the meaning of any Quality (Medium in particular) in terms of data usability. + A warning is printed if a Check associates a good Flag to bad Quality or a bad Flag to good Quality. + +![](images/qo_flag_conversion_04.svg) + +* **Timespans not covered by a given QO are filled with Flag 1 (Unknown Quality).** + In other words, if an object was missing during a part of the run, we can state that the data quality is not known. + +![](images/qo_flag_conversion_05.svg) + +* **Overlapping or adjacent Flags with the same ID, comment and source (QO name) are merged.**. + This happens even if they were associated with different Qualities, e.g. Bad and Medium. + Order of Flag arrival does not matter. + +![](images/qo_flag_conversion_06.svg) +![](images/qo_flag_conversion_07.svg) + +* **Flag 1 (Unknown Quality) is overwritten by any other Flag.** + This allows us to return Null Quality when there is not enough statistics to determine data quality, but it can be suppressed later, once we can return Good/Medium/Bad. + +![](images/qo_flag_conversion_08.svg) + +* **Good and Bad flags do not affect each other, they may coexist.** + +![](images/qo_flag_conversion_09.svg) + +* **Flags for different QOs (QO names) do not affect each other. + Flag 1 (Unknown Quality) is added separately for each.** + +![](images/qo_flag_conversion_10.svg) + +# Solving performance issues + +Problems with performance in message passing systems like QC usually manifest in backpressure seen in input channels of processes which are too slow. +QC processes usually use one worker thread, thus one can also observe that they use a full CPU core when struggling to consume incoming data. +When observing performance issues with QC setups, consider the following actions to improve it. + +## Dispatcher + +Dispatcher will usually cause backpressure when it is requested to sample too much data. +In particular, copying many small messages takes more time than less messages of equivalent size. +To improve the performance: +* reduce the sampling rate +* disable unused sampling policies +* adapt the data format to pack data in fewer messages +* when in need of 100% data, do not use Data Sampling, but connect to the data source directly + +## QC Tasks + +QC Tasks are implemented by the users, thus the maximum possible input data throughput largely depends on the task implementation. +If a QC Task cannot cope with the input messages, consider: +* sampling less data +* using performance measurement tools (like `perf top`) to understand where the task spends the most time and optimize this part of code +* if one task instance processes data, spawn one task per machine and merge the result objects instead + +## Mergers + +The performance of Mergers depends on the type of objects being merged, as well as their number and size. +The following points might help avoid backpressure: +* increase QC tasks cycle duration +* use less or smaller objects +* if an object has its custom Merge() method, check if it could be optimized +* enable multi-layer Mergers to split the computations across multiple processes (config parameter "mergersPerLayer") + +# Understanding and reducing memory footprint + +When developing a QC module, please be considerate in terms of memory usage. +Large histograms could be optionally enabled/disabled depending on the context that the QC is ran. +Investigate if reducing the bin size (e.g. TH2D to TH2F) would still provide satisfactory results. +Consider loading only the parts of detector geometry which are being used by a given task. + +## Analysing memory usage with valgrind + +0) Install valgrind, if not yet installed + +1) Run the QC workflow with argument `--child-driver 'valgrind --tool=massif'` (as well as any file reader / processing workflow you need to obtain data in QC) + +2) The workflow will run and save files massif.out. + +3) Generate a report for the file corresponding to the PID of the QC task: + +``` +ms_print massif.out.976329 > massif_abc_task.log +``` + +4) The generated report contains: +* the command used to run the process +* graph of the memory usage +* grouped call stacks of all memory allocations on the heap (above certain threshold) within certain time intervals. + The left-most call contains all the calls which lead to it, represented on the right. + For example, the call stack below means that the AbcTask created a TH2F histogram in the initalize method at the line + AbcTask.cxx:82, which was 51,811,760B. In total, 130,269,568B worth of TH2F histograms were created in this time interval. + +``` +98.56% (256,165,296B) (heap allocation functions) malloc/new/new[], --alloc-fns, etc. +->50.12% (130,269,568B) 0xFCBD1A6: TArrayF::Set(int) [clone .part.0] (TArrayF.cxx:111) +| ->50.12% (130,269,568B) 0xEC1DB1C: TH2F::TH2F(char const*, char const*, int, double, double, int, double, double) (TH2.cxx:3573) +| ->19.93% (51,811,760B) 0x32416518: make_unique (unique_ptr.h:1065) +| | ->19.93% (51,811,760B) 0x32416518: o2::quality_control_modules::det::AbcTask::initialize(o2::framework::InitContext&) (AbcTask.cxx:82) +``` + +5) To get a lightweight and more digestible output, consider running the massif report through the following command to get the summary of the calls only within a QC module. This essentially tells you how much memory a given line allocates. + +``` +[O2PDPSuite/latest] ~/alice/test-rss $> grep quality_control_modules massif_abc_task.log | sed 's/^.*[0-9][0-9]\.[0-9][0-9]\% //g' | sort | uniq +(242,371,376B) 0x324166B2: o2::quality_control_modules::det::AbcTask::initialize(o2::framework::InitContext&) (AbcTask.cxx:88) +(4,441,008B) 0x3241633F: o2::quality_control_modules::det::AbcTask::initialize(o2::framework::InitContext&) (AbcTask.cxx:76) +(4,441,008B) 0x32416429: o2::quality_control_modules::det::AbcTask::initialize(o2::framework::InitContext&) (AbcTask.cxx:79) +(51,811,760B) 0x32416518: o2::quality_control_modules::det::AbcTask::initialize(o2::framework::InitContext&) (AbcTask.cxx:82) +(51,811,760B) 0x324165EB: o2::quality_control_modules::det::AbcTask::initialize(o2::framework::InitContext&) (AbcTask.cxx:85) +``` + +6) Consider reducing the size and number of the biggest histogram. Consider disabling histograms which will not be useful for async QC (no allocations, no startPublishing). + +[← Go back to Post-Processing](PostProcessing.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to Configuration →](Configuration.md) \ No newline at end of file From 400ebaa5c4d96408c65cf02a7bdb61aaf7796930 Mon Sep 17 00:00:00 2001 From: Barthelemy Date: Thu, 8 May 2025 14:43:59 +0200 Subject: [PATCH 04/10] [QC-1286] extract qcdb and rename advanced --- doc/{Advanced.md => Miscellaneous.md} | 212 ++------------------------ doc/QCDB.md | 191 +++++++++++++++++++++++ 2 files changed, 201 insertions(+), 202 deletions(-) rename doc/{Advanced.md => Miscellaneous.md} (66%) create mode 100644 doc/QCDB.md diff --git a/doc/Advanced.md b/doc/Miscellaneous.md similarity index 66% rename from doc/Advanced.md rename to doc/Miscellaneous.md index d501bdffe9..ae23d19a26 100644 --- a/doc/Advanced.md +++ b/doc/Miscellaneous.md @@ -1,27 +1,15 @@ -Advanced topics +Miscellaneous --- - * [Advanced topics](#advanced-topics) -* [CCDB / QCDB](#ccdb--qcdb) - * [Accessing objects in CCDB](#accessing-objects-in-ccdb) - * [Accessing from a Postprocessing task](#accessing-from-a-postprocessing-task) - * [Access GRP objects with GRP Geom Helper](#access-grp-objects-with-grp-geom-helper) - * [Global Tracking Data Request helper](#global-tracking-data-request-helper) - * [Custom metadata](#custom-metadata) - * [Details on the data storage format in the CCDB](#details-on-the-data-storage-format-in-the-ccdb) - * [Data storage format before v0.14 and ROOT 6.18](#data-storage-format-before-v014-and-root-618) - * [Local CCDB setup](#local-ccdb-setup) - * [Instructions to move an object in the QCDB](#instructions-to-move-an-object-in-the-qcdb) * [Asynchronous Data and Monte Carlo QC operations](#asynchronous-data-and-monte-carlo-qc-operations) * [QCG](#qcg) * [Display a non-standard ROOT object in QCG](#display-a-non-standard-root-object-in-qcg) * [Canvas options](#canvas-options) * [Local QCG (QC GUI) setup](#local-qcg-qc-gui-setup) -* [Miscellaneous](#miscellaneous) * [Data Sampling monitoring](#data-sampling-monitoring) * [Monitoring metrics](#monitoring-metrics) * [Common check IncreasingEntries](#common-check-increasingentries) @@ -36,185 +24,6 @@ Advanced topics [← Go back to Post-processing](PostProcessing.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to Frequently Asked Questions →](FAQ.md) -# CCDB / QCDB - -## Accessing objects in CCDB - -The MonitorObjects generated by Quality Control are stored in a dedicated repository (QCDB), which is based on CCDB. -The run conditions, on the other hand, are located in another, separate database. - -The recommended way (excluding postprocessing) to access these conditions is to use a `Lifetime::Condition` DPL input, which can be requested as in the query below: - -```json - "tasks": { - "MyTask": { - ... - "dataSource": { - "type": "direct", - "query": "randomcluster:MFT/COMPCLUSTERS/0;cldict:MFT/CLUSDICT/0?lifetime=condition&ccdb-path=MFT/Calib/ClusterDictionary" - }, - } - }, -``` - -The timestamp of the CCDB object will be aligned with the data timestamp. - -If a task needs both sampled input and a CCDB object, it is advised to use two data sources as such: - -```json - "tasks": { - "MyTask": { - ... - "dataSources": [{ - "type": "dataSamplingPolicy", - "name": "mftclusters" - }, { - "type": "direct", - "query": "cldict:MFT/CLUSDICT/0?lifetime=condition&ccdb-path=MFT/Calib/ClusterDictionary" - }], - } - }, -``` - -The requested CCDB object can be accessed like any other DPL input in `monitorData`: - -``` -void QcMFTClusterTask::monitorData(o2::framework::ProcessingContext& ctx) -{ -... - auto mDictPtr = ctx.inputs().get("cldict"); -``` - -Geometry and General Run Parameters (GRP) can be also accessed with the [GRP Geom Helper](#access-grp-objects-with-grp-geom-helper). - -If your task accesses CCDB objects using `UserCodeInterface::retrieveConditionAny`, please migrate to using one of the methods mentioned above. - -### Accessing from a Postprocessing task - -PostProcessingTasks do not take DPL inputs, so in this case `ConditionAccess::retrieveConditionAny` should be used (it's inherited by `PostProcessingInterface` and any children). - -## Access GRP objects with GRP Geom Helper - -To get GRP objects via a central facility, add the following structure to the task definition and set its values -according to the needs. - -```json - "myTask": { - ... - "grpGeomRequest" : { - "geomRequest": "None", "": "Available options are \"None\", \"Aligned\", \"Ideal\", \"Alignements\"", - "askGRPECS": "false", - "askGRPLHCIF": "false", - "askGRPMagField": "false", - "askMatLUT": "false", - "askTime": "false", - "askOnceAllButField": "false", - "needPropagatorD": "false" - } - } -``` - -The requested objects will be available via [`GRPGeomHelper::instance()`](https://github.com/AliceO2Group/AliceO2/blob/dev/Detectors/Base/include/DetectorsBase/GRPGeomHelper.h) singleton. - -## Global Tracking Data Request helper - -To retrieve tracks and clusters for specific detectors or detector combinations, one can use the [`DataRequest`](https://github.com/AliceO2Group/AliceO2/blob/dev/DataFormats/Detectors/GlobalTracking/include/DataFormatsGlobalTracking/RecoContainer.h) helper. -By adding the following structure to a QC task, the corresponding `InputSpecs` will be automatically added to the task. - -```json - "myTask": { - ... - "globalTrackingDataRequest": { - "canProcessTracks" : "ITS,ITS-TPC", - "requestTracks" : "ITS,TPC-TRD", - "canProcessClusters" : "TPC", - "requestClusters" : "TPC", - "mc" : "false" - } - } -``` - -Then, the corresponding tracks and clusters can be retrieved in the code using `RecoContainer`: - -```c++ -void MyTask::monitorData(o2::framework::ProcessingContext& ctx) -{ - o2::globaltracking::RecoContainer recoData; - if (auto dataRequest = getGlobalTrackingDataRequest()) { - recoData.collectData(ctx, *dataRequest); - } -} -``` - -## Custom metadata - -One can add custom metadata on the QC objects produced in a QC task. -Simply call `ObjectsManager::addMetadata(...)`, like in - -``` - // add a metadata on histogram mHistogram, key is "custom" and value "34" - getObjectsManager()->addMetadata(mHistogram->GetName(), "custom", "34"); -``` - -This metadata will end up in the QCDB. - -It is also possible to add or update metadata of a MonitorObject directly: - -``` - MonitorObject* mo = getMonitorObject(objectName); - mo->addOrUpdateMetadata(key, value); -``` - -## Details on the data storage format in the CCDB - -Each MonitorObject is stored as a TFile in the CCDB. -It is therefore possible to easily open it with ROOT when loaded with alienv. It also seamlessly supports class schema evolution. - -The MonitorObjects are stored at a path which is enforced by the qc framework : `/qc//MO//object/name` -Note that the name of the object can contain slashes (`/`) in order to build a sub-tree visible in the GUI. -The detector name and the taskname are set in the config file : - -```json -"tasks": { - "QcTask": { <---------- task name - "active": "true", - "className": "o2::quality_control_modules::skeleton::SkeletonTask", - "moduleName": "QcSkeleton", - "detectorName": "TST", <---------- detector name -``` - -The quality is stored as a CCDB metadata of the object. - -### Data storage format before v0.14 and ROOT 6.18 - -Before September 2019, objects were serialized with TMessage and stored as _blobs_ in the CCDB. The main drawback was the loss of the corresponding streamer infos leading to problems when the class evolved or when accessing the data outside the QC framework. - -The QC framework is nevertheless backward compatible and can handle the old and the new storage system. - -## Local CCDB setup - -Having a central ccdb for test (ccdb-test) is handy but also means that everyone can access, modify or delete the data. If you prefer to have a local instance of the CCDB, for example in your lab or on your development machine, follow these instructions. - -1. Download the local repository service from - -2. The service can simply be run with - `java -jar local.jar` - -It will start listening by default on port 8080. This can be changed either with the java parameter “tomcat.port” or with the environment variable “TOMCAT_PORT”. Similarly the default listening address is 127.0.0.1 and it can be changed with the java parameter “tomcat.address” or with the environment variable “TOMCAT_ADDRESS” to something else (for example ‘*’ to listen on all interfaces). - -By default the local repository is located in /tmp/QC (or java.io.tmpdir/QC to be more precise). You can change this location in a similar way by setting the java parameter “file.repository.location” or the environment variable “FILE_REPOSITORY_LOCATION”. - -The address of the CCDB will have to be updated in the Tasks config file. - -At the moment, the description of the REST api can be found in this document : - -## Instructions to move an object in the QCDB - -The script `o2-qc-repo-move-objects` lets the user move an object, and thus all the versions attached to it. E.g.: - -``` -python3 o2-qc-repo-move-objects --url http://ccdb-test.cern.ch:8080 --path qc/TST/MO/Bob --new-path qc/TST/MO/Bob2 --log-level 10 -``` # Asynchronous Data and Monte Carlo QC operations @@ -292,9 +101,8 @@ These methods must be called after the objects has been published, i.e. after th To install and run the QCG locally please follow these instructions : -# Miscellaneous -## Data Sampling monitoring +# Data Sampling monitoring To have the monitoring metrics for the Data Sampling (the Dispatcher) sent to a specific sink (like influxdb), add the option `--monitoring-backend` when launching the DPL workflow. For example: @@ -306,7 +114,7 @@ This will actually send the monitoring data of _all_ DPL devices to this databas **Note for mac users**: if you get a crash and the message "std::exception::what: send_to: Message too long", it means that you have to adapt a `udp` parameter. You can check the datagram size via `sudo sysctl net.inet.udp.maxdgram`. If it says something less than 64 kB, then increase size: `sudo sysctl -w net.inet.udp.maxdgram=65535` -## Monitoring metrics +# Monitoring metrics The QC framework publishes monitoring metrics concerning data/object rates, which are published to the monitoring backend specified in the `"monitoring.url"` parameter in config files. If QC is run in standalone mode (no AliECS) and with @@ -315,7 +123,7 @@ printing them as soon as they are reported, please also add `--monitoring-backen One can also enable publishing metrics related to CPU/memory usage. To do so, use `--resources-monitoring `. -## Common check `IncreasingEntries` +# Common check `IncreasingEntries` This check make sures that the number of entries has increased in the past cycle(s). If not, it will display a pavetext on the plot and set the quality to bad. @@ -345,7 +153,7 @@ The number of cycles during which we tolerate increasing (or not respectively) t In the example above, the quality goes to bad when there are 3 cycles in a row with no increase in the number of entries. -## Common check `TrendCheck` +# Common check `TrendCheck` This check compares the last point of a trending plot with some minimum and maximum thresholds. @@ -396,7 +204,7 @@ The position and size of the text label that shows the check result can also be The values are relative to the canvas size, so in the example above the label width is 50% of the canvas width and the label height is 10% of the canvas height. -#### Full configuration example +### Full configuration example ```json "MyTrendingCheckFixed": { @@ -485,11 +293,11 @@ The values are relative to the canvas size, so in the example above the label wi } ``` -## Update the shmem segment size of a detector +# Update the shmem segment size of a detector In consul go to `o2/runtime/aliecs/defaults` and modify the file corresponding to the detector: [det]_qc_shm_segment_size -### Readout chain (optional) +## Readout chain (optional) In this second example, we are going to use the Readout as our data source. This example assumes that Readout has been compiled beforehand (`aliBuild build Readout --defaults o2`). @@ -530,11 +338,11 @@ o2-qc-run-readout | o2-qc --config json://${QUALITYCONTROL_ROOT}/etc/readout.jso The data sampling is configured to sample 1% of the data as the readout should run by default at full speed. -#### Getting real data from readout +### Getting real data from readout See [these instructions for readout](ModulesDevelopment.md#readout) and [these for O2 utilities](ModulesDevelopment.md#dpl-workflow). -#### Readout data format as received by the Task +### Readout data format as received by the Task The header is an O2 header populated with data from the header built by the Readout. The payload received is a 2MB (configurable) data page made of CRU pages (8kB). diff --git a/doc/QCDB.md b/doc/QCDB.md new file mode 100644 index 0000000000..86e9ee5b7b --- /dev/null +++ b/doc/QCDB.md @@ -0,0 +1,191 @@ + +QCDB +--- + + + + + + +## Accessing objects in CCDB + +The MonitorObjects generated by Quality Control are stored in a dedicated repository (QCDB), which is based on CCDB. +The run conditions, on the other hand, are located in another, separate database. + +The recommended way (excluding postprocessing) to access these conditions is to use a `Lifetime::Condition` DPL input, which can be requested as in the query below: + +```json + "tasks": { + "MyTask": { + ... + "dataSource": { + "type": "direct", + "query": "randomcluster:MFT/COMPCLUSTERS/0;cldict:MFT/CLUSDICT/0?lifetime=condition&ccdb-path=MFT/Calib/ClusterDictionary" + }, + } + }, +``` + +The timestamp of the CCDB object will be aligned with the data timestamp. + +If a task needs both sampled input and a CCDB object, it is advised to use two data sources as such: + +```json + "tasks": { + "MyTask": { + ... + "dataSources": [{ + "type": "dataSamplingPolicy", + "name": "mftclusters" + }, { + "type": "direct", + "query": "cldict:MFT/CLUSDICT/0?lifetime=condition&ccdb-path=MFT/Calib/ClusterDictionary" + }], + } + }, +``` + +The requested CCDB object can be accessed like any other DPL input in `monitorData`: + +``` +void QcMFTClusterTask::monitorData(o2::framework::ProcessingContext& ctx) +{ +... + auto mDictPtr = ctx.inputs().get("cldict"); +``` + +Geometry and General Run Parameters (GRP) can be also accessed with the [GRP Geom Helper](#access-grp-objects-with-grp-geom-helper). + +If your task accesses CCDB objects using `UserCodeInterface::retrieveConditionAny`, please migrate to using one of the methods mentioned above. + +### Accessing from a Postprocessing task + +PostProcessingTasks do not take DPL inputs, so in this case `ConditionAccess::retrieveConditionAny` should be used (it's inherited by `PostProcessingInterface` and any children). + +## Access GRP objects with GRP Geom Helper + +To get GRP objects via a central facility, add the following structure to the task definition and set its values +according to the needs. + +```json + "myTask": { + ... + "grpGeomRequest" : { + "geomRequest": "None", "": "Available options are \"None\", \"Aligned\", \"Ideal\", \"Alignements\"", + "askGRPECS": "false", + "askGRPLHCIF": "false", + "askGRPMagField": "false", + "askMatLUT": "false", + "askTime": "false", + "askOnceAllButField": "false", + "needPropagatorD": "false" + } + } +``` + +The requested objects will be available via [`GRPGeomHelper::instance()`](https://github.com/AliceO2Group/AliceO2/blob/dev/Detectors/Base/include/DetectorsBase/GRPGeomHelper.h) singleton. + +## Global Tracking Data Request helper + +To retrieve tracks and clusters for specific detectors or detector combinations, one can use the [`DataRequest`](https://github.com/AliceO2Group/AliceO2/blob/dev/DataFormats/Detectors/GlobalTracking/include/DataFormatsGlobalTracking/RecoContainer.h) helper. +By adding the following structure to a QC task, the corresponding `InputSpecs` will be automatically added to the task. + +```json + "myTask": { + ... + "globalTrackingDataRequest": { + "canProcessTracks" : "ITS,ITS-TPC", + "requestTracks" : "ITS,TPC-TRD", + "canProcessClusters" : "TPC", + "requestClusters" : "TPC", + "mc" : "false" + } + } +``` + +Then, the corresponding tracks and clusters can be retrieved in the code using `RecoContainer`: + +```c++ +void MyTask::monitorData(o2::framework::ProcessingContext& ctx) +{ + o2::globaltracking::RecoContainer recoData; + if (auto dataRequest = getGlobalTrackingDataRequest()) { + recoData.collectData(ctx, *dataRequest); + } +} +``` + +## Custom metadata + +One can add custom metadata on the QC objects produced in a QC task. +Simply call `ObjectsManager::addMetadata(...)`, like in + +``` + // add a metadata on histogram mHistogram, key is "custom" and value "34" + getObjectsManager()->addMetadata(mHistogram->GetName(), "custom", "34"); +``` + +This metadata will end up in the QCDB. + +It is also possible to add or update metadata of a MonitorObject directly: + +``` + MonitorObject* mo = getMonitorObject(objectName); + mo->addOrUpdateMetadata(key, value); +``` + +## Details on the data storage format in the CCDB + +Each MonitorObject is stored as a TFile in the CCDB. +It is therefore possible to easily open it with ROOT when loaded with alienv. It also seamlessly supports class schema evolution. + +The MonitorObjects are stored at a path which is enforced by the qc framework : `/qc//MO//object/name` +Note that the name of the object can contain slashes (`/`) in order to build a sub-tree visible in the GUI. +The detector name and the taskname are set in the config file : + +```json +"tasks": { + "QcTask": { <---------- task name + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonTask", + "moduleName": "QcSkeleton", + "detectorName": "TST", <---------- detector name +``` + +The quality is stored as a CCDB metadata of the object. + +### Data storage format before v0.14 and ROOT 6.18 + +Before September 2019, objects were serialized with TMessage and stored as _blobs_ in the CCDB. The main drawback was the loss of the corresponding streamer infos leading to problems when the class evolved or when accessing the data outside the QC framework. + +The QC framework is nevertheless backward compatible and can handle the old and the new storage system. + +## Local CCDB setup + +Having a central ccdb for test (ccdb-test) is handy but also means that everyone can access, modify or delete the data. If you prefer to have a local instance of the CCDB, for example in your lab or on your development machine, follow these instructions. + +1. Download the local repository service from + +2. The service can simply be run with + `java -jar local.jar` + +It will start listening by default on port 8080. This can be changed either with the java parameter “tomcat.port” or with the environment variable “TOMCAT_PORT”. Similarly the default listening address is 127.0.0.1 and it can be changed with the java parameter “tomcat.address” or with the environment variable “TOMCAT_ADDRESS” to something else (for example ‘*’ to listen on all interfaces). + +By default the local repository is located in /tmp/QC (or java.io.tmpdir/QC to be more precise). You can change this location in a similar way by setting the java parameter “file.repository.location” or the environment variable “FILE_REPOSITORY_LOCATION”. + +The address of the CCDB will have to be updated in the Tasks config file. + +At the moment, the description of the REST api can be found in this document : + +## Instructions to move an object in the QCDB + +The script `o2-qc-repo-move-objects` lets the user move an object, and thus all the versions attached to it. E.g.: + +``` +python3 o2-qc-repo-move-objects --url http://ccdb-test.cern.ch:8080 --path qc/TST/MO/Bob --new-path qc/TST/MO/Bob2 --log-level 10 +``` + +--- + +[← Go back to Post-processing](PostProcessing.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to Configuration →](Configuration.md) + From b20cd2451d26b2e5695a817fb393fa7a99e4c9ab Mon Sep 17 00:00:00 2001 From: Barthelemy Date: Thu, 8 May 2025 15:17:29 +0200 Subject: [PATCH 05/10] [QC-1286] up to modules dev --- README.md | 1 - doc/Configuration.md | 9 ++--- doc/FLPsuite.md | 2 +- doc/Miscellaneous.md | 20 +++++------ doc/ModulesDevelopment.md | 69 +++++++++++++++++------------------- doc/PostProcessing.md | 2 +- doc/QuickStart.md | 18 ++++------ doc/images/Architecture.png | Bin 97010 -> 92732 bytes 8 files changed, 56 insertions(+), 65 deletions(-) diff --git a/README.md b/README.md index 50d962f093..a52ee11ae1 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,6 @@ For a general overview of our (O2) software, organization and processes, please * [Environment loading](doc/QuickStart.md#environment-loading) * [Execution](doc/QuickStart.md#execution) * [Basic workflow](doc/QuickStart.md#basic-workflow) - * [Readout chain (optional)](doc/QuickStart.md#readout-chain-optional) * [Post-processing example](doc/QuickStart.md#post-processing-example) * [Modules development](doc/ModulesDevelopment.md) * [Context](doc/ModulesDevelopment.md#context) diff --git a/doc/Configuration.md b/doc/Configuration.md index 9f5cc2a075..673ea18e64 100644 --- a/doc/Configuration.md +++ b/doc/Configuration.md @@ -147,7 +147,6 @@ In production at P2 and in staging, some common items are defined globally in th * consul * conditionDB * bookkeeping -* It is mandatory to use them by including the file: @@ -578,7 +577,9 @@ http://alio2-cr1-hv-mvs00.cern.ch:32188/components/qc/ANY/any/templating_demo?pr --> the file is included and the condition is true thus we have an extra line. -## Definition and access of simple user-defined task configuration ("taskParameters") +## Definition and access of user-defined task configuration + +### Simple and limited approach ("taskParameters") The new, extended, way of defining such parameters, not only in Tasks but also in Checks, Aggregators and PP tasks, is described in the next section. @@ -597,7 +598,7 @@ The syntax is It is accessed with : `mCustomParameters["myOwnKey"]`. -## Definition and access of user-defined configuration ("extendedTaskParameters") +### Advanced approach with beam and run type parametrization ("extendedTaskParameters") User code, whether it is a Task, a Check, an Aggregator or a PostProcessing task, can access custom parameters declared in the configuration file. They are stored inside an object of type `CustomParameters` named `mCustomParameters`, which is a protected member of `TaskInterface`. @@ -718,4 +719,4 @@ In a postprocessing task, it is available in the objects manager: `getObjectsMan --- -[← Go back to Framework](Framework.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to Frequently Asked Questions →](FAQ.md) +[← Go back to Framework](Framework.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to FLP Suite →](FLPsuite.md) diff --git a/doc/FLPsuite.md b/doc/FLPsuite.md index 27161edd90..81a058d5df 100644 --- a/doc/FLPsuite.md +++ b/doc/FLPsuite.md @@ -205,4 +205,4 @@ See the details [here](https://github.com/AliceO2Group/QualityControl/blob/maste --- -[← Go back to Post-processing](PostProcessing.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to Configuration →](Configuration.md) +[← Go back to Configuration](Configuration.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to Miscellaneous →](Miscellaneous.md) diff --git a/doc/Miscellaneous.md b/doc/Miscellaneous.md index ae23d19a26..c20edc2256 100644 --- a/doc/Miscellaneous.md +++ b/doc/Miscellaneous.md @@ -10,15 +10,15 @@ Miscellaneous * [Display a non-standard ROOT object in QCG](#display-a-non-standard-root-object-in-qcg) * [Canvas options](#canvas-options) * [Local QCG (QC GUI) setup](#local-qcg-qc-gui-setup) - * [Data Sampling monitoring](#data-sampling-monitoring) - * [Monitoring metrics](#monitoring-metrics) - * [Common check IncreasingEntries](#common-check-increasingentries) - * [Common check TrendCheck](#common-check-trendcheck) - * [Full configuration example](#full-configuration-example) - * [Update the shmem segment size of a detector](#update-the-shmem-segment-size-of-a-detector) - * [Readout chain (optional)](#readout-chain-optional) - * [Getting real data from readout](#getting-real-data-from-readout) - * [Readout data format as received by the Task](#readout-data-format-as-received-by-the-task) +* [Data Sampling monitoring](#data-sampling-monitoring) +* [Monitoring metrics](#monitoring-metrics) +* [Common check IncreasingEntries](#common-check-increasingentries) +* [Common check TrendCheck](#common-check-trendcheck) + * [Full configuration example](#full-configuration-example) +* [Update the shmem segment size of a detector](#update-the-shmem-segment-size-of-a-detector) + * [Readout chain (optional)](#readout-chain-optional) + * [Getting real data from readout](#getting-real-data-from-readout) + * [Readout data format as received by the Task](#readout-data-format-as-received-by-the-task) [← Go back to Post-processing](PostProcessing.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to Frequently Asked Questions →](FAQ.md) @@ -360,4 +360,4 @@ To change the fraction of the data being monitored, change the option `fraction` --- -[← Go back to Post-processing](PostProcessing.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to Configuration →](Configuration.md) +[← Go back to FLP Suite](FLPsuite.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to FAQ →](FAQ.md) diff --git a/doc/ModulesDevelopment.md b/doc/ModulesDevelopment.md index 75e5ec6cba..cc23560c23 100644 --- a/doc/ModulesDevelopment.md +++ b/doc/ModulesDevelopment.md @@ -3,38 +3,35 @@ -* [Modules development](#modules-development) - * [Context](#context) - * [QC architecture](#qc-architecture) - * [DPL](#dpl) - * [Data Sampling](#data-sampling) - * [Custom Data Sampling Condition](#custom-data-sampling-condition) - * [Bypassing the Data Sampling](#bypassing-the-data-sampling) - * [Code Organization](#code-organization) - * [Developing with aliBuild/alienv](#developing-with-alibuildalienv) - * [User-defined modules](#user-defined-modules) - * [Repository](#repository) - * [Paths](#paths) - * [Module creation](#module-creation) - * [Test run](#test-run) - * [Saving the QC objects in a local file](#saving-the-qc-objects-in-a-local-file) - * [Modification of the Task](#modification-of-the-task) - * [Check](#check) - * [Configuration](#configuration) - * [Implementation](#implementation) - * [Results](#results) - * [Quality Aggregation](#quality-aggregation) - * [Quick try](#quick-try) - * [Configuration](#configuration-1) - * [Implementation](#implementation-1) - * [Naming convention](#naming-convention) - * [Committing code](#committing-code) - * [Data sources](#data-sources) - * [Readout](#readout) - * [DPL workflow](#dpl-workflow) - * [Run number and other run attributes (period, pass type, provenance)](#run-number-and-other-run-attributes-period-pass-type-provenance) - * [A more advanced example](#a-more-advanced-example) - * [Monitoring](#monitoring) +* [Context](#context) + * [QC architecture](#qc-architecture) + * [DPL](#dpl) + * [Data Sampling](#data-sampling) + * [Code Organization](#code-organization) + * [Developing with aliBuild/alienv](#developing-with-alibuildalienv) + * [User-defined modules](#user-defined-modules) + * [Repository](#repository) + * [Paths](#paths) +* [Module creation](#module-creation) +* [Test run](#test-run) + * [Saving the QC objects in a local file](#saving-the-qc-objects-in-a-local-file) +* [Modification of the Task](#modification-of-the-task) +* [Check](#check) + * [Configuration](#configuration) + * [Implementation](#implementation) + * [Results](#results) +* [Quality Aggregation](#quality-aggregation) + * [Quick try](#quick-try) + * [Configuration](#configuration-1) + * [Implementation](#implementation-1) +* [Naming convention](#naming-convention) +* [Committing code](#committing-code) +* [Data sources](#data-sources) + * [Readout](#readout) + * [DPL workflow](#dpl-workflow) +* [Run number and other run attributes (period, pass type, provenance)](#run-number-and-other-run-attributes-period-pass-type-provenance) +* [A more advanced example](#a-more-advanced-example) +* [Monitoring](#monitoring) [← Go back to Quickstart](QuickStart.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to Post-processing →](PostProcessing.md) @@ -47,13 +44,13 @@ Before developing a module, one should have a bare idea of what the QualityContr ![alt text](images/Architecture.png) -The main data flow is represented in blue. Data samples are selected by the Data Sampling (not represented) and sent to the QC tasks, either on the same machines or on other machines. The tasks produce TObjects, usually histograms, encapsulated in a MonitorObject that are merged (if needed) and then checked. The checkers output a QualityObject along with the MonitorObjects which might have been modified. The MonitorObjects and the QualityObjects are stored in the repository. The QualityObjects can also optionally be aggregated by the Aggregators to produce additional QualityObjects that are also saved in the database. +The main data flow is represented in blue. Data samples are selected by the Data Sampling (not represented) and sent to the QC tasks, either on the same machines or on other machines. The tasks produce TObjects, usually histograms, encapsulated in a MonitorObject that are merged (if needed) and then checked. The checkers output a QualityObject along with the MonitorObjects which might have been modified. The MonitorObjects and the QualityObjects are stored in the repository. The QualityObjects can also be aggregated by the Aggregators to produce additional QualityObjects that are also saved in the database. Asynchronously, the Post-processing can retrieve MonitorObjects from the database when certain events happen (new version of an object, new run) and produce new TObjects such as a trending plot. ### DPL -[Data Processing Layer](https://github.com/AliceO2Group/AliceO2/blob/dev/Framework/Core/README.md) is a software framework developed as a part of O2 project. It structurizes the computing into units called _Data Processors_ - processes that communicate with each other via messages. DPL takes care of generating and running the processing topology out of user declaration code, serializing and deserializing messages, providing the data processors with all the anticipated messages for a given timestamp and much more. Each piece of data is characterized by its `DataHeader`, which consists (among others) of `dataOrigin`, `dataDescription` and `SubSpecification` - for example `{"MFT", "TRACKS", 0}`. +[Data Processing Layer](https://github.com/AliceO2Group/AliceO2/blob/dev/Framework/Core/README.md) is a software framework developed as a part of the O2 project. It defines and runs workflows made of _DataProcessors_ (aka _Devices_) communicating with each other via messages. An example of a workflow definition which describes the processing steps (_Data Processors_), their inputs and their outputs can be seen in [runBasic.cxx](https://github.com/AliceO2Group/QualityControl/blob/master/Framework/src/runBasic.cxx). In the QC we define the workflows in files whose names are prefixed with `run`. @@ -150,13 +147,11 @@ One can of course build using `aliBuild` (`aliBuild build --defaults o2 QualityC After the initial use of `aliBuild`, which is necessary, the correct way of building is to load the environment with `alienv` and then go to the build directory and run `make` or `ninja`. ``` -alienv load QualityControl/latest +alienv enter QualityControl/latest cd sw/BUILD/QualityControl-latest/QualityControl make -j8 install # or ninja -j8 install , also adapt to the number of cores available ``` -If you need to use the QCG or Readout, load `O2Suite` instead of `QualityControl`. - ### User-defined modules The Quality Control uses _plugins_ to load the actual code to be executed by the _Tasks_, the _Checks_, the _Aggregators_ and the _PostProcessing_. A module, or plugin, can contain one or several of these classes. They must subclass the corresponding interfaces, for example `TaskInterface.h` or `CheckInterface.h`. We use the Template Method Design Pattern. diff --git a/doc/PostProcessing.md b/doc/PostProcessing.md index 83aa08b5e4..c23aff4470 100644 --- a/doc/PostProcessing.md +++ b/doc/PostProcessing.md @@ -1155,4 +1155,4 @@ Use the Activity which leaves the run number empty, but indicate the pass and pe } ``` -[← Go back to Modules Development](ModulesDevelopment.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to Advanced Topics →](Advanced.md) +[← Go back to Modules Development](ModulesDevelopment.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to Framework →](Framework.md) diff --git a/doc/QuickStart.md b/doc/QuickStart.md index 7350245759..149fa79f7d 100644 --- a/doc/QuickStart.md +++ b/doc/QuickStart.md @@ -3,17 +3,13 @@ -* [QuickStart](#quickstart) - * [Read this first!](#read-this-first) - * [Requirements](#requirements) - * [Setup](#setup) - * [Environment loading](#environment-loading) - * [Execution](#execution) - * [Basic workflow](#basic-workflow) - * [Readout chain (optional)](#readout-chain-optional) - * [Getting real data from readout](#getting-real-data-from-readout) - * [Readout data format as received by the Task](#readout-data-format-as-received-by-the-task) - * [Post-processing example](#post-processing-example) +* [Read this first!](#read-this-first) +* [Requirements](#requirements) +* [Setup](#setup) + * [Environment loading](#environment-loading) +* [Execution](#execution) + * [Basic workflow](#basic-workflow) + * [Post-processing example](#post-processing-example) [↑ Go to the Table of Content ↑](../README.md) | [Continue to Modules Development →](ModulesDevelopment.md) diff --git a/doc/images/Architecture.png b/doc/images/Architecture.png index b953afad010e49b72d55604652f2d24d50c03e10..176bd39fc9b3612ee6e88bf0e3d0801783150cf2 100644 GIT binary patch literal 92732 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

NocI5<$*zuF^-%W}zLg8>) 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%0A!=N(x=LR0Byf3 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

  • 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 J8R+fD{|Bpz{v7}S From c8d37c7eaa9c28703f538b1674736404b5c19037 Mon Sep 17 00:00:00 2001 From: Barthelemy Date: Fri, 9 May 2025 16:46:05 +0200 Subject: [PATCH 06/10] [QC-1286] up to Framework.md --- doc/Configuration.md | 8 +-- doc/Framework.md | 110 ++++++++++++++++++++------------------ doc/Miscellaneous.md | 21 ++++++++ doc/ModulesDevelopment.md | 3 +- doc/PostProcessing.md | 26 +++++---- doc/QuickStart.md | 2 +- 6 files changed, 98 insertions(+), 72 deletions(-) diff --git a/doc/Configuration.md b/doc/Configuration.md index 673ea18e64..ed6b229024 100644 --- a/doc/Configuration.md +++ b/doc/Configuration.md @@ -191,12 +191,12 @@ the "tasks" path. "moduleName": "QcSkeleton", "": "Library name. It can be found in CMakeLists of the detector module.", "detectorName": "TST", "": "3-letter code of the detector.", "critical": "true", "": "if false the task is allowed to die without stopping the workflow, default: true", - "cycleDurationSeconds": "10", "": "Cycle duration (how often objects are published), 10 seconds minimum.", + "cycleDurationSeconds": "60", "": "Cycle duration (how often objects are published), 10 seconds minimum.", "": "The first cycle will be randomly shorter. ", "": "Alternatively, one can specify different cycle durations for different periods. The last item in cycleDurations will be used for the rest of the duration whatever the period. The first cycle will be randomly shorter.", "cycleDurations": [ - {"cycleDurationSeconds": 10, "validitySeconds": 35}, - {"cycleDurationSeconds": 12, "validitySeconds": 1} + {"cycleDurationSeconds": 60, "validitySeconds": 35}, + {"cycleDurationSeconds": 120, "validitySeconds": 1} ], "maxNumberCycles": "-1", "": "Number of cycles to perform. Use -1 for infinite.", "disableLastCycle": "true", "": "Last cycle, upon EndOfStream, is not published. (default: false)", @@ -716,6 +716,8 @@ In an Aggregator, it is returned by `getActivity()`. In a postprocessing task, it is available in the objects manager: `getObjectsManager()->getActivity()` +TODO we miss the definition of the datasampling policies + --- diff --git a/doc/Framework.md b/doc/Framework.md index 6421f0ef4a..64465de874 100644 --- a/doc/Framework.md +++ b/doc/Framework.md @@ -5,36 +5,64 @@ Framework -* [Plugging the QC to an existing DPL workflow](#plugging-the-qc-to-an-existing-dpl-workflow) -* [Production of QC objects outside this framework](#production-of-qc-objects-outside-this-framework) -* [Multi-node setups](#multi-node-setups) -* [Batch processing](#batch-processing) -* [Moving window](#moving-window) -* [Monitor cycles](#monitor-cycles) -* [Writing a DPL data producer](#writing-a-dpl-data-producer) -* [Custom merging](#custom-merging) -* [Critical, resilient and non-critical tasks](#critical-resilient-and-non-critical-tasks) -* [QC with DPL Analysis](#qc-with-dpl-analysis) - * [Uploading objects to QCDB](#uploading-objects-to-qcdb) -* [Propagating Check results to RCT in Bookkeeping](#propagating-check-results-to-rct-in-bookkeeping) - * [Conversion details](#conversion-details) -* [Solving performance issues](#solving-performance-issues) - * [Dispatcher](#dispatcher) - * [QC Tasks](#qc-tasks-1) - * [Mergers](#mergers) -* [Understanding and reducing memory footprint](#understanding-and-reducing-memory-footprint) + * [Framework](#framework) + * [Plugging the QC to an existing DPL workflow](#plugging-the-qc-to-an-existing-dpl-workflow) + * [Production of QC objects outside this framework](#production-of-qc-objects-outside-this-framework) + * [Multi-node setups](#multi-node-setups) + * [Batch processing](#batch-processing) + * [Moving window](#moving-window) + * [Monitor cycles](#monitor-cycles) + * [Custom merging](#custom-merging) + * [Critical, resilient and non-critical tasks](#critical-resilient-and-non-critical-tasks) + * [QC with DPL Analysis](#qc-with-dpl-analysis) + * [Propagating Check results to RCT in Bookkeeping](#propagating-check-results-to-rct-in-bookkeeping) + * [Solving performance issues](#solving-performance-issues) + * [Understanding and reducing memory footprint](#understanding-and-reducing-memory-footprint) ## Plugging the QC to an existing DPL workflow -Your existing DPL workflow can simply be considered a publisher. Therefore, replace `o2-qc-run-producer` with your own workflow. +Your existing DPL workflow can simply be considered a publisher. Therefore, you can simply replace `o2-qc-run-producer` with your own workflow in the examples we have seen so far. -For example, if TPC wants to monitor the output `{"TPC", "CLUSTERS"}` of the workflow `o2-qc-run-tpcpid`, modify the config file to point to the correct data and do : +As an example, if TPC wants to monitor the workflow `o2-qc-run-tpcpid`, modify the config file to point to the correct data and do : ``` o2-qc-run-tpcpid | o2-qc --config json://${QUALITYCONTROL_ROOT}/etc/tpcQCPID.json ``` +The config file `tpcQCPID.json` has to specify what output we want to monitor. E.g. if the tpc workflow has the output `{"TPC", "CLUSTERS"}`, we should add a dataSamplingPolicy for it and refer to it in a task : + +``` + "dataSamplingPolicies": [ + { + "id": "tpc-cluster", "":"ID we refer to" + "active": "true", + "machines": [], + "query" : "data:TPC/CLUSTERS", "":"The query to match the output in the workflow" + "samplingConditions": [ + { + "condition": "random", + "fraction": "0.1", + } + ], + "blocking": "false" + }, +(...) + "someTask": { + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonTask", + "moduleName": "QcSkeleton", + "detectorName": "TST", + "cycleDurationSeconds": "60", + "dataSource": { + "type": "dataSamplingPolicy", + "name": "tpc-cluster", "":"Refer to the ID of the policy" + } + } +``` +The "query" syntax of the policy is the same as the one used in the DPL. It must match the output of another device, whether it is in the same workflow or in a piped one. +The `binding` (first part, before the colon) is used in the path of the stored objects and thus we encourage to use the task name to avoid confusion. Moreover, the `origin` (first element after the colon) is used as detectorName. + ## Production of QC objects outside this framework QC objects (e.g. histograms) are typically produced in a QC task. @@ -51,15 +79,12 @@ Let be a device in the main data flow that produces a histogram on a channel def "externalTasks": { "External-1": { "active": "true", - "query": "External-1:TST/HISTO/0", "": "Query specifying where the objects to be checked and stored are coming from. Use the task name as binding. The origin (e.g. TST) is used as detector name for the objects." + "query": "External-1:TST/HISTO/0" , "":"Query specifying where the objects to be checked and stored are coming from. Use the task name as binding. The origin (e.g. TST) is used as detector name for the objects." } }, "checks": { ``` -The "query" syntax is the same as the one used in the DPL and in the Dispatcher. It must match the output of another device, whether it is in the same workflow or in a piped one. -The `binding` (first part, before the colon) is used in the path of the stored objects and thus we encourage to use the task name to avoid confusion. Moreover, the `origin` (first element after the colon) is used as detectorName. - ### Example 1: basic As a basic example, we are going to produce histograms with the HistoProducer and collect them with the QC. The configuration is in [basic-external-histo.json](https://github.com/AliceO2Group/QualityControl/blob/master/Framework/basic-external-histo.json). An external task is defined and named "External-1" (see subsection above). It is then used in the Check QCCheck : @@ -115,9 +140,9 @@ o2-qc-run-producer | o2-qc-run-histo-producer --producers 3 --histograms 3 | o2- ## Multi-node setups -During the data-taking Quality Control runs on a distributed computing system. Some QC Tasks are -executed on dedicated QC servers, while others run on FLPs and EPNs. In the first case, messages -coming from Data Sampling should reach QC servers where they are processed. In the latter case, +During the data-taking, Quality Control runs on a distributed computing system. Some QC Tasks are +executed on dedicated QC servers, while others run on FLPs and EPNs. In the latter case, messages +coming from Data Sampling should reach QC servers where they are processed. In the first case, locally produced Monitor Objects should be merged on QC servers and then have Checks run on them. By **remote QC tasks** we mean those which run on QC servers (**remote machines**), while **local QC Tasks** run on FLPs and EPNs (**local machines**). @@ -459,26 +484,7 @@ It is possible to specify various durations for different period of times. It is In this example, a cycle of 60 seconds is used for the first 5 minutes (300 seconds), then a cycle of 3 minutes (180 seconds) between 5 minutes and 10 minutes after SOR, and finally a cycle of 5 minutes for the rest of the run. The last `validitySeconds` is not used and is just applied for the rest of the run. -## Writing a DPL data producer - -For your convenience, and although it does not lie within the QC scope, we would like to document how to write a simple data producer in the DPL. The DPL documentation can be found [here](https://github.com/AliceO2Group/AliceO2/blob/dev/Framework/Core/README.md) and for questions please head to the [forum](https://alice-talk.web.cern.ch/). - -As an example we take the `DataProducerExample` that you can find in the QC repository. It is produces a number. By default it will be 1s but one can specify with the parameter `my-param` a different number. It is made of 3 files : - -* [runDataProducerExample.cxx](../Framework/src/runDataProducerExample.cxx) : - This is an executable with a basic data producer in the Data Processing Layer. - There are 2 important functions here : - * `customize(...)` to add parameters to the executable. Note that it must be written before the includes for the dataProcessing. - * `defineDataProcessing(...)` to define the workflow to be ran, in our case the device(s) publishing the number. -* [DataProducerExample.h](../Framework/include/QualityControl/DataProducerExample.h) : - The key elements are : - 1. The include `#include ` - 2. The function `getDataProducerExampleSpec(...)` which must return a `DataProcessorSpec` i.e. the description of a device (name, inputs, outputs, algorithm) - 3. The function `getDataProducerExampleAlgorithm` which must return an `AlgorithmSpec` i.e. the actual algorithm that produces the data. -* [DataProducerExample.cxx](../Framework/src/DataProducerExample.cxx) : - This is just the implementation of the header described just above. You will probably want to modify `getDataProducerExampleSpec` and the inner-most block of `getDataProducerExampleAlgorithm`. You might be taken aback by the look of this function, if you don't know what a _lambda_ is just ignore it and write your code inside the accolades. -You will probably write it in your detector's O2 directory rather than in the QC repository. ## Custom merging @@ -647,13 +653,13 @@ Null QOs are marked with purple. ![](images/qo_flag_conversion_10.svg) -# Solving performance issues +## Solving performance issues Problems with performance in message passing systems like QC usually manifest in backpressure seen in input channels of processes which are too slow. QC processes usually use one worker thread, thus one can also observe that they use a full CPU core when struggling to consume incoming data. When observing performance issues with QC setups, consider the following actions to improve it. -## Dispatcher +### Dispatcher Dispatcher will usually cause backpressure when it is requested to sample too much data. In particular, copying many small messages takes more time than less messages of equivalent size. @@ -663,7 +669,7 @@ To improve the performance: * adapt the data format to pack data in fewer messages * when in need of 100% data, do not use Data Sampling, but connect to the data source directly -## QC Tasks +### QC Tasks QC Tasks are implemented by the users, thus the maximum possible input data throughput largely depends on the task implementation. If a QC Task cannot cope with the input messages, consider: @@ -671,7 +677,7 @@ If a QC Task cannot cope with the input messages, consider: * using performance measurement tools (like `perf top`) to understand where the task spends the most time and optimize this part of code * if one task instance processes data, spawn one task per machine and merge the result objects instead -## Mergers +### Mergers The performance of Mergers depends on the type of objects being merged, as well as their number and size. The following points might help avoid backpressure: @@ -680,14 +686,14 @@ The following points might help avoid backpressure: * if an object has its custom Merge() method, check if it could be optimized * enable multi-layer Mergers to split the computations across multiple processes (config parameter "mergersPerLayer") -# Understanding and reducing memory footprint +## Understanding and reducing memory footprint When developing a QC module, please be considerate in terms of memory usage. Large histograms could be optionally enabled/disabled depending on the context that the QC is ran. Investigate if reducing the bin size (e.g. TH2D to TH2F) would still provide satisfactory results. Consider loading only the parts of detector geometry which are being used by a given task. -## Analysing memory usage with valgrind +### Analysing memory usage with valgrind 0) Install valgrind, if not yet installed diff --git a/doc/Miscellaneous.md b/doc/Miscellaneous.md index c20edc2256..292b164f53 100644 --- a/doc/Miscellaneous.md +++ b/doc/Miscellaneous.md @@ -358,6 +358,27 @@ To change the fraction of the data being monitored, change the option `fraction` "fraction": "0.01", ``` +## Writing a DPL data producer + +For your convenience, and although it does not lie within the QC scope, we would like to document how to write a simple data producer in the DPL. The DPL documentation can be found [here](https://github.com/AliceO2Group/AliceO2/blob/dev/Framework/Core/README.md) and for questions please head to the [forum](https://alice-talk.web.cern.ch/). + +As an example we take the `DataProducerExample` that you can find in the QC repository. It is produces a number. By default it will be 1s but one can specify with the parameter `my-param` a different number. It is made of 3 files : + +* [runDataProducerExample.cxx](../Framework/src/runDataProducerExample.cxx) : + This is an executable with a basic data producer in the Data Processing Layer. + There are 2 important functions here : + * `customize(...)` to add parameters to the executable. Note that it must be written before the includes for the dataProcessing. + * `defineDataProcessing(...)` to define the workflow to be ran, in our case the device(s) publishing the number. +* [DataProducerExample.h](../Framework/include/QualityControl/DataProducerExample.h) : + The key elements are : + 1. The include `#include ` + 2. The function `getDataProducerExampleSpec(...)` which must return a `DataProcessorSpec` i.e. the description of a device (name, inputs, outputs, algorithm) + 3. The function `getDataProducerExampleAlgorithm` which must return an `AlgorithmSpec` i.e. the actual algorithm that produces the data. +* [DataProducerExample.cxx](../Framework/src/DataProducerExample.cxx) : + This is just the implementation of the header described just above. You will probably want to modify `getDataProducerExampleSpec` and the inner-most block of `getDataProducerExampleAlgorithm`. You might be taken aback by the look of this function, if you don't know what a _lambda_ is just ignore it and write your code inside the accolades. + +You will probably write it in your detector's O2 directory rather than in the QC repository. + --- [← Go back to FLP Suite](FLPsuite.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to FAQ →](FAQ.md) diff --git a/doc/ModulesDevelopment.md b/doc/ModulesDevelopment.md index cc23560c23..ae5810e9a8 100644 --- a/doc/ModulesDevelopment.md +++ b/doc/ModulesDevelopment.md @@ -84,8 +84,7 @@ Data Sampling is used by Quality Control to feed the tasks with data. Below we p "samplingConditions": [ { "condition": "random", - "fraction": "0.1", - "seed": "1234" + "fraction": "0.1" } ], "blocking": "false" diff --git a/doc/PostProcessing.md b/doc/PostProcessing.md index c23aff4470..80bd1fd350 100644 --- a/doc/PostProcessing.md +++ b/doc/PostProcessing.md @@ -3,20 +3,18 @@ -* [Post-processing](#post-processing) - * [The post-processing framework](#the-post-processing-framework) - * [Post-processing interface](#post-processing-interface) - * [Configuration](#configuration) - * [Definition and access of user-specific configuration](#definition-and-access-of-user-specific-configuration) - * [Running it](#running-it) - * [Convenience classes](#convenience-classes) - * [The TrendingTask class](#the-trendingtask-class) - * [The SliceTrendingTask class](#the-slicetrendingtask-class) - * [The ReferenceComparatorTask class](#the-referencecomparatortask-class) - * [The CcdbInspectorTask class](#the-ccdbinspectortask-class) - * [The QualityTask class](#the-qualitytask-class) - * [The BigScreen class](#the-bigscreen-class) - * [More examples](#more-examples) +* [The post-processing framework](#the-post-processing-framework) + * [Post-processing interface](#post-processing-interface) + * [Configuration](#configuration) + * [Running it](#running-it) +* [Convenience classes](#convenience-classes) + * [The TrendingTask class](#the-trendingtask-class) + * [The SliceTrendingTask class](#the-slicetrendingtask-class) + * [The ReferenceComparatorTask class](#the-referencecomparatortask-class) + * [The CcdbInspectorTask class](#the-ccdbinspectortask-class) + * [The QualityTask class](#the-qualitytask-class) + * [The BigScreen class](#the-bigscreen-class) +* [More examples](#more-examples) [← Go back to Modules Development](ModulesDevelopment.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to Advanced Topics →](Advanced.md) diff --git a/doc/QuickStart.md b/doc/QuickStart.md index 149fa79f7d..3344778fd6 100644 --- a/doc/QuickStart.md +++ b/doc/QuickStart.md @@ -114,7 +114,7 @@ For now we can see below the definition of a task. `moduleName` and `className` "active": "true", "className": "o2::quality_control_modules::skeleton::SkeletonTask", "moduleName": "QcSkeleton", - "cycleDurationSeconds": "10", "": "10 seconds minimum", + "cycleDurationSeconds": "60", "": "60 seconds minimum", (...) ``` Try and change the name of the task by replacing `QcTask` by a name of your choice (there are 2 places to update in the config file!). Relaunch the workflows. You should now see the object published under a different directory in the QCG. From bff913c772120644b4571e8d2d7eadeb638b6747 Mon Sep 17 00:00:00 2001 From: Barthelemy Date: Mon, 12 May 2025 09:13:20 +0200 Subject: [PATCH 07/10] [QC-1286] up to flp suite --- doc/Configuration.md | 7 +-- doc/FAQ.md | 2 +- doc/FLPsuite.md | 2 +- doc/Framework.md | 14 +++++ doc/Miscellaneous.md | 3 - doc/ModulesDevelopment.md | 14 ----- doc/PostProcessing.md | 4 +- doc/QCDB.md | 129 ++++++++++++++++++++------------------ doc/QuickStart.md | 2 - 9 files changed, 88 insertions(+), 89 deletions(-) diff --git a/doc/Configuration.md b/doc/Configuration.md index ed6b229024..dfd8b8e171 100644 --- a/doc/Configuration.md +++ b/doc/Configuration.md @@ -88,9 +88,8 @@ should not be present in real configuration files. "host": "ccdb-test.cern.ch:8080", "": "URL of a DB.", "maxObjectSize": "2097152", "": "[Bytes, default=2MB] Maximum size allowed, larger objects are rejected." }, - "Activity": { "": ["Configuration of a QC Activity (Run). This structure is subject to", - "change or the values might come from other source (e.g. ECS+Bookkeeping)." ], - "number": "42", "": "Activity number.", + "Activity": { "": ["Configuration of a QC Activity (Run). DO NOT USE IN PRODUCTION! " ], + "number": "42", "": "Activity number. ", "type": "PHYSICS", "": "Activity type.", "periodName": "", "": "Period name - e.g. LHC22c, LHC22c1b_test", "passName": "", "": "Pass type - e.g. spass, cpass1", @@ -721,4 +720,4 @@ TODO we miss the definition of the datasampling policies --- -[← Go back to Framework](Framework.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to FLP Suite →](FLPsuite.md) +[← Go back to Post-Processing](PostProcessing.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to QCDB →](QCDB.md) diff --git a/doc/FAQ.md b/doc/FAQ.md index 5c1276fc89..d4b153818f 100644 --- a/doc/FAQ.md +++ b/doc/FAQ.md @@ -120,4 +120,4 @@ in the config: "maxObjectSize": "2097152", "": "[Bytes, default=2MB] Maximum size allowed, larger objects are rejected." ``` -[← Go back to Advanced Topics](Advanced.md) | [↑ Go to the Table of Content ↑](../README.md) +[← Go back to Miscellaneous](Miscellaneous.md) | [↑ Go to the Table of Content ↑](../README.md) diff --git a/doc/FLPsuite.md b/doc/FLPsuite.md index 81a058d5df..1a7525de83 100644 --- a/doc/FLPsuite.md +++ b/doc/FLPsuite.md @@ -205,4 +205,4 @@ See the details [here](https://github.com/AliceO2Group/QualityControl/blob/maste --- -[← Go back to Configuration](Configuration.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to Miscellaneous →](Miscellaneous.md) +[← Go back to QCDB](QCDB.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to Miscellaneous →](Miscellaneous.md) diff --git a/doc/Framework.md b/doc/Framework.md index 64465de874..95218a8cfa 100644 --- a/doc/Framework.md +++ b/doc/Framework.md @@ -18,6 +18,7 @@ Framework * [Propagating Check results to RCT in Bookkeeping](#propagating-check-results-to-rct-in-bookkeeping) * [Solving performance issues](#solving-performance-issues) * [Understanding and reducing memory footprint](#understanding-and-reducing-memory-footprint) + * [Monitoring](#monitoring) ## Plugging the QC to an existing DPL workflow @@ -736,4 +737,17 @@ ms_print massif.out.976329 > massif_abc_task.log 6) Consider reducing the size and number of the biggest histogram. Consider disabling histograms which will not be useful for async QC (no allocations, no startPublishing). +## Monitoring + +The QC uses the [O2 Monitoring](https://github.com/AliceO2Group/Monitoring/) library to monitor metrics. +The user code has access to an instance of the Monitoring via the variable `mMonitoring`. +It can be used this way: +``` +mMonitoring->send({ 42, "my/metric" }); // send the value 42 keyed with "my/metric" +``` +By default the Monitoring will be printed in the terminal. If a proper Monitoring system +is setup, one can update the monitoring url in the config file to point to it. + +--- + [← Go back to Post-Processing](PostProcessing.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to Configuration →](Configuration.md) \ No newline at end of file diff --git a/doc/Miscellaneous.md b/doc/Miscellaneous.md index 292b164f53..2d29a6a7d1 100644 --- a/doc/Miscellaneous.md +++ b/doc/Miscellaneous.md @@ -21,9 +21,6 @@ Miscellaneous * [Readout data format as received by the Task](#readout-data-format-as-received-by-the-task) -[← Go back to Post-processing](PostProcessing.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to Frequently Asked Questions →](FAQ.md) - - # Asynchronous Data and Monte Carlo QC operations diff --git a/doc/ModulesDevelopment.md b/doc/ModulesDevelopment.md index ae5810e9a8..a5a8dbd77c 100644 --- a/doc/ModulesDevelopment.md +++ b/doc/ModulesDevelopment.md @@ -11,10 +11,8 @@ * [Developing with aliBuild/alienv](#developing-with-alibuildalienv) * [User-defined modules](#user-defined-modules) * [Repository](#repository) - * [Paths](#paths) * [Module creation](#module-creation) * [Test run](#test-run) - * [Saving the QC objects in a local file](#saving-the-qc-objects-in-a-local-file) * [Modification of the Task](#modification-of-the-task) * [Check](#check) * [Configuration](#configuration) @@ -31,7 +29,6 @@ * [DPL workflow](#dpl-workflow) * [Run number and other run attributes (period, pass type, provenance)](#run-number-and-other-run-attributes-period-pass-type-provenance) * [A more advanced example](#a-more-advanced-example) -* [Monitoring](#monitoring) [← Go back to Quickstart](QuickStart.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to Post-processing →](PostProcessing.md) @@ -590,17 +587,6 @@ or o2-qc-run-advanced --no-qc | o2-qc --config json://${QUALITYCONTROL_ROOT}/etc/advanced.json ``` -## Monitoring - -The QC uses the [O2 Monitoring](https://github.com/AliceO2Group/Monitoring/) library to monitor metrics. -The user code has access to an instance of the Monitoring via the variable `mMonitoring`. -It can be used this way: -``` -mMonitoring->send({ 42, "my/metric" }); // send the value 42 keyed with "my/metric" -``` -By default the Monitoring will be printed in the terminal. If a proper Monitoring system -is setup, one can update the monitoring url in the config file to point to it. - --- [← Go back to Quickstart](QuickStart.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to Post-processing →](PostProcessing.md) diff --git a/doc/PostProcessing.md b/doc/PostProcessing.md index 80bd1fd350..add28a74be 100644 --- a/doc/PostProcessing.md +++ b/doc/PostProcessing.md @@ -17,8 +17,6 @@ * [More examples](#more-examples) -[← Go back to Modules Development](ModulesDevelopment.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to Advanced Topics →](Advanced.md) - ## The post-processing framework This framework is intended for planned post-processing of objects generated by QC Tasks, Checks and correlating them with other data. The most common use-cases include correlation and trending of different properties of the detectors. @@ -1153,4 +1151,4 @@ Use the Activity which leaves the run number empty, but indicate the pass and pe } ``` -[← Go back to Modules Development](ModulesDevelopment.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to Framework →](Framework.md) +[← Go back to Framework](Framework.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to Configuration →](Configuration.md) diff --git a/doc/QCDB.md b/doc/QCDB.md index 86e9ee5b7b..21f9a93c3d 100644 --- a/doc/QCDB.md +++ b/doc/QCDB.md @@ -5,14 +5,73 @@ QCDB + * [QCDB vs CCDB](#qcdb-vs-ccdb) + * [Details on the data storage format in the QCDB](#details-on-the-data-storage-format-in-the-qcdb) + * [Custom metadata for QC objects in the QCDB](#custom-metadata-for-qc-objects-in-the-qcdb) + * [Instructions to move an object in the QCDB](#instructions-to-move-an-object-in-the-qcdb) + * [Accessing objects in CCDB](#accessing-objects-in-ccdb) + * [Access GRP objects with GRP Geom Helper](#access-grp-objects-with-grp-geom-helper) + * [Global Tracking Data Request helper](#global-tracking-data-request-helper) + * [Local CCDB setup](#local-ccdb-setup) -## Accessing objects in CCDB +## QCDB vs CCDB + +The MonitorObjects generated by Quality Control are stored in a dedicated repository called **QCDB**. +The run conditions, on the other hand, are located in another, separate database, called **CCDB**. + +Both are based on a technology called _CCDB_ which does not help with the confusion... + +## Details on the data storage format in the QCDB + +Each MonitorObject is stored as a TFile in the QCDB. +It is therefore possible to easily open it with ROOT when loaded with alienv. It also seamlessly supports class schema evolution. + +The MonitorObjects are stored at a path which is enforced by the qc framework : `/qc//MO//object/name` +Note that the name of the object can contain slashes (`/`) in order to build a sub-tree visible in the GUI. +The detector name and the taskname are set in the config file : + +```json +"tasks": { + "QcTask": { <---------- task name + "active": "true", + "className": "o2::quality_control_modules::skeleton::SkeletonTask", + "moduleName": "QcSkeleton", + "detectorName": "TST", <---------- detector name +``` + +The quality is stored as a CCDB metadata of the object. + +## Custom metadata for QC objects in the QCDB + +One can add custom metadata on the QC objects produced in a QC task. +Simply call `ObjectsManager::addMetadata(...)`, like in + +``` + // add a metadata on histogram mHistogram, key is "custom" and value "34" + getObjectsManager()->addMetadata(mHistogram->GetName(), "custom", "34"); +``` + +This metadata will end up in the _QCDB_. + +It is also possible to add or update metadata of a MonitorObject directly: + +``` + MonitorObject* mo = getMonitorObject(objectName); + mo->addOrUpdateMetadata(key, value); +``` + +## Instructions to move an object in the QCDB + +The script `o2-qc-repo-move-objects` lets the user move an object, and thus all the versions attached to it. E.g.: + +``` +python3 o2-qc-repo-move-objects --url http://ccdb-test.cern.ch:8080 --path qc/TST/MO/Bob --new-path qc/TST/MO/Bob2 --log-level 10 +``` -The MonitorObjects generated by Quality Control are stored in a dedicated repository (QCDB), which is based on CCDB. -The run conditions, on the other hand, are located in another, separate database. +## Accessing objects in CCDB -The recommended way (excluding postprocessing) to access these conditions is to use a `Lifetime::Condition` DPL input, which can be requested as in the query below: +The recommended way (excluding postprocessing) to access the run conditions in the _CCDB_ is to use a `Lifetime::Condition` DPL input, which can be requested as in the query below: ```json "tasks": { @@ -26,9 +85,9 @@ The recommended way (excluding postprocessing) to access these conditions is to }, ``` -The timestamp of the CCDB object will be aligned with the data timestamp. +The timestamp of the _CCDB_ object will be aligned with the data timestamp. -If a task needs both sampled input and a CCDB object, it is advised to use two data sources as such: +If a task needs both sampled input and a _CCDB_ object, it is advised to use two data sources as such: ```json "tasks": { @@ -45,7 +104,7 @@ If a task needs both sampled input and a CCDB object, it is advised to use two d }, ``` -The requested CCDB object can be accessed like any other DPL input in `monitorData`: +The requested _CCDB_ object can be accessed like any other DPL input in `monitorData`: ``` void QcMFTClusterTask::monitorData(o2::framework::ProcessingContext& ctx) @@ -56,7 +115,7 @@ void QcMFTClusterTask::monitorData(o2::framework::ProcessingContext& ctx) Geometry and General Run Parameters (GRP) can be also accessed with the [GRP Geom Helper](#access-grp-objects-with-grp-geom-helper). -If your task accesses CCDB objects using `UserCodeInterface::retrieveConditionAny`, please migrate to using one of the methods mentioned above. +If your task accesses _CCDB_ objects using `UserCodeInterface::retrieveConditionAny`, please migrate to using one of the methods mentioned above. ### Accessing from a Postprocessing task @@ -115,51 +174,6 @@ void MyTask::monitorData(o2::framework::ProcessingContext& ctx) } ``` -## Custom metadata - -One can add custom metadata on the QC objects produced in a QC task. -Simply call `ObjectsManager::addMetadata(...)`, like in - -``` - // add a metadata on histogram mHistogram, key is "custom" and value "34" - getObjectsManager()->addMetadata(mHistogram->GetName(), "custom", "34"); -``` - -This metadata will end up in the QCDB. - -It is also possible to add or update metadata of a MonitorObject directly: - -``` - MonitorObject* mo = getMonitorObject(objectName); - mo->addOrUpdateMetadata(key, value); -``` - -## Details on the data storage format in the CCDB - -Each MonitorObject is stored as a TFile in the CCDB. -It is therefore possible to easily open it with ROOT when loaded with alienv. It also seamlessly supports class schema evolution. - -The MonitorObjects are stored at a path which is enforced by the qc framework : `/qc//MO//object/name` -Note that the name of the object can contain slashes (`/`) in order to build a sub-tree visible in the GUI. -The detector name and the taskname are set in the config file : - -```json -"tasks": { - "QcTask": { <---------- task name - "active": "true", - "className": "o2::quality_control_modules::skeleton::SkeletonTask", - "moduleName": "QcSkeleton", - "detectorName": "TST", <---------- detector name -``` - -The quality is stored as a CCDB metadata of the object. - -### Data storage format before v0.14 and ROOT 6.18 - -Before September 2019, objects were serialized with TMessage and stored as _blobs_ in the CCDB. The main drawback was the loss of the corresponding streamer infos leading to problems when the class evolved or when accessing the data outside the QC framework. - -The QC framework is nevertheless backward compatible and can handle the old and the new storage system. - ## Local CCDB setup Having a central ccdb for test (ccdb-test) is handy but also means that everyone can access, modify or delete the data. If you prefer to have a local instance of the CCDB, for example in your lab or on your development machine, follow these instructions. @@ -177,15 +191,8 @@ The address of the CCDB will have to be updated in the Tasks config file. At the moment, the description of the REST api can be found in this document : -## Instructions to move an object in the QCDB - -The script `o2-qc-repo-move-objects` lets the user move an object, and thus all the versions attached to it. E.g.: - -``` -python3 o2-qc-repo-move-objects --url http://ccdb-test.cern.ch:8080 --path qc/TST/MO/Bob --new-path qc/TST/MO/Bob2 --log-level 10 -``` --- -[← Go back to Post-processing](PostProcessing.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to Configuration →](Configuration.md) +[← Go back to Configuration](Configuration.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to FLP Suite →](FLPsuite.md) diff --git a/doc/QuickStart.md b/doc/QuickStart.md index 3344778fd6..74fda9be0f 100644 --- a/doc/QuickStart.md +++ b/doc/QuickStart.md @@ -12,8 +12,6 @@ * [Post-processing example](#post-processing-example) -[↑ Go to the Table of Content ↑](../README.md) | [Continue to Modules Development →](ModulesDevelopment.md) - ## Read this first! This page will give you a basic idea of the QC and how to run it. Please read it *in its entirety* and run the commands along the way. Do not start developing your module before you have reached the next section called "Modules Development". Also, make sure you have pulled the latest QC version. From cb12a4ea22c2530d3efdc7ffb8449fa2dd81b46b Mon Sep 17 00:00:00 2001 From: Barthelemy Date: Mon, 12 May 2025 13:31:17 +0200 Subject: [PATCH 08/10] [QC-1286] Full TOC --- README.md | 159 ++++++++++++++++---------------------- doc/Configuration.md | 1 - doc/FAQ.md | 31 +------- doc/Framework.md | 3 +- doc/Miscellaneous.md | 15 ++-- doc/ModulesDevelopment.md | 4 +- 6 files changed, 77 insertions(+), 136 deletions(-) diff --git a/README.md b/README.md index a52ee11ae1..b8c9411012 100644 --- a/README.md +++ b/README.md @@ -20,19 +20,15 @@ For a general overview of our (O2) software, organization and processes, please * [Post-processing example](doc/QuickStart.md#post-processing-example) * [Modules development](doc/ModulesDevelopment.md) * [Context](doc/ModulesDevelopment.md#context) - * [QC architecture](doc/ModulesDevelopment.md#qc-architecture) + * [QC architecture](doc/ModulesDevelopment.md#qc-architecture) * [DPL](doc/ModulesDevelopment.md#dpl) * [Data Sampling](doc/ModulesDevelopment.md#data-sampling) - * [Custom Data Sampling Condition](doc/ModulesDevelopment.md#custom-data-sampling-condition) - * [Bypassing the Data Sampling](doc/ModulesDevelopment.md#bypassing-the-data-sampling) * [Code Organization](doc/ModulesDevelopment.md#code-organization) * [Developing with aliBuild/alienv](doc/ModulesDevelopment.md#developing-with-alibuildalienv) * [User-defined modules](doc/ModulesDevelopment.md#user-defined-modules) * [Repository](doc/ModulesDevelopment.md#repository) - * [Paths](doc/ModulesDevelopment.md#paths) * [Module creation](doc/ModulesDevelopment.md#module-creation) * [Test run](doc/ModulesDevelopment.md#test-run) - * [Saving the QC objects in a local file](doc/ModulesDevelopment.md#saving-the-qc-objects-in-a-local-file) * [Modification of the Task](doc/ModulesDevelopment.md#modification-of-the-task) * [Check](doc/ModulesDevelopment.md#check) * [Configuration](doc/ModulesDevelopment.md#configuration) @@ -49,95 +45,72 @@ For a general overview of our (O2) software, organization and processes, please * [DPL workflow](doc/ModulesDevelopment.md#dpl-workflow) * [Run number and other run attributes (period, pass type, provenance)](doc/ModulesDevelopment.md#run-number-and-other-run-attributes-period-pass-type-provenance) * [A more advanced example](doc/ModulesDevelopment.md#a-more-advanced-example) - * [Monitoring](doc/ModulesDevelopment.md#monitoring) +* [Framework](doc/Framework.md) + * [Plugging the QC to an existing DPL workflow](doc/Framework.md#plugging-the-qc-to-an-existing-dpl-workflow) + * [Production of QC objects outside this framework](doc/Framework.md#production-of-qc-objects-outside-this-framework) + * [Multi-node setups](doc/Framework.md#multi-node-setups) + * [Batch processing](doc/Framework.md#batch-processing) + * [Moving window](doc/Framework.md#moving-window) + * [Monitor cycles](doc/Framework.md#monitor-cycles) + * [Custom merging](doc/Framework.md#custom-merging) + * [Critical, resilient and non-critical tasks](doc/Framework.md#critical-resilient-and-non-critical-tasks) + * [QC with DPL Analysis](doc/Framework.md#qc-with-dpl-analysis) + * [Propagating Check results to RCT in Bookkeeping](doc/Framework.md#propagating-check-results-to-rct-in-bookkeeping) + * [Solving performance issues](doc/Framework.md#solving-performance-issues) + * [Understanding and reducing memory footprint](doc/Framework.md#understanding-and-reducing-memory-footprint) + * [Monitoring](doc/Framework.md#monitoring) * [Post-processing](doc/PostProcessing.md) - * [The post-processing framework](doc/PostProcessing.md#the-post-processing-framework) - * [Post-processing interface](doc/PostProcessing.md#post-processing-interface) - * [Configuration](doc/PostProcessing.md#configuration) - * [Definition and access of user-specific configuration](doc/PostProcessing.md#definition-and-access-of-user-specific-configuration) - * [Running it](doc/PostProcessing.md#running-it) - * [Convenience classes](doc/PostProcessing.md#convenience-classes) - * [The TrendingTask class](doc/PostProcessing.md#the-trendingtask-class) - * [The SliceTrendingTask class](doc/PostProcessing.md#the-slicetrendingtask-class) - * [The QualityTask class](doc/PostProcessing.md#the-qualitytask-class) - * [The BigScreen class](doc/PostProcessing.md#the-bigscreen-class) - * [More examples](doc/PostProcessing.md#more-examples) -* [Advanced topics](doc/Advanced.md) - * [Framework](doc/Advanced.md#framework) - * [Plugging the QC to an existing DPL workflow](doc/Advanced.md#plugging-the-qc-to-an-existing-dpl-workflow) - * [Production of QC objects outside this framework](doc/Advanced.md#production-of-qc-objects-outside-this-framework) - * [Multi-node setups](doc/Advanced.md#multi-node-setups) - * [Batch processing](doc/Advanced.md#batch-processing) - * [Moving window](doc/Advanced.md#moving-window) - * [Monitor cycles](doc/Advanced.md#monitor-cycles) - * [Writing a DPL data producer](doc/Advanced.md#writing-a-dpl-data-producer) - * [Custom merging](doc/Advanced.md#custom-merging) - * [Critical, resilient and non-critical tasks](doc/Advanced.md#critical-resilient-and-non-critical-tasks) - * [QC with DPL Analysis](doc/Advanced.md#qc-with-dpl-analysis) - * [Uploading objects to QCDB](doc/Advanced.md#uploading-objects-to-qcdb) - * [Propagating Check results to RCT in Bookkeeping](doc/Advanced.md#propagating-check-results-to-rct-in-bookkeeping) - * [Conversion details](doc/Advanced.md#conversion-details) - * [Solving performance issues](doc/Advanced.md#solving-performance-issues) - * [Dispatcher](doc/Advanced.md#dispatcher) - * [QC Tasks](doc/Advanced.md#qc-tasks-1) - * [Mergers](doc/Advanced.md#mergers) - * [Understanding and reducing memory footprint](doc/Advanced.md#understanding-and-reducing-memory-footprint) - * [Analysing memory usage with valgrind](doc/Advanced.md#analysing-memory-usage-with-valgrind) - * [CCDB / QCDB](doc/Advanced.md#ccdb--qcdb) - * [Accessing objects in CCDB](doc/Advanced.md#accessing-objects-in-ccdb) - * [Access GRP objects with GRP Geom Helper](doc/Advanced.md#access-grp-objects-with-grp-geom-helper) - * [Global Tracking Data Request helper](doc/Advanced.md#global-tracking-data-request-helper) - * [Custom metadata](doc/Advanced.md#custom-metadata) - * [Details on the data storage format in the CCDB](doc/Advanced.md#details-on-the-data-storage-format-in-the-ccdb) - * [Local CCDB setup](doc/Advanced.md#local-ccdb-setup) - * [Instructions to move an object in the QCDB](doc/Advanced.md#instructions-to-move-an-object-in-the-qcdb) - * [Asynchronous Data and Monte Carlo QC operations](doc/Advanced.md#asynchronous-data-and-monte-carlo-qc-operations) - * [QCG](doc/Advanced.md#qcg) - * [Display a non-standard ROOT object in QCG](doc/Advanced.md#display-a-non-standard-root-object-in-qcg) - * [Canvas options](doc/Advanced.md#canvas-options) - * [Local QCG (QC GUI) setup](doc/Advanced.md#local-qcg-qc-gui-setup) - * [FLP Suite](doc/Advanced.md#flp-suite) - * [Developing QC modules on a machine with FLP suite](doc/Advanced.md#developing-qc-modules-on-a-machine-with-flp-suite) - * [Switch detector in the workflow readout-dataflow](doc/Advanced.md#switch-detector-in-the-workflow-readout-dataflow) - * [Get all the task output to the infologger](doc/Advanced.md#get-all-the-task-output-to-the-infologger) - * [Using a different config file with the general QC](doc/Advanced.md#using-a-different-config-file-with-the-general-qc) - * [Enable the repo cleaner](doc/Advanced.md#enable-the-repo-cleaner) - * [Configuration](doc/Advanced.md#configuration-1) - * [Merging multiple configuration files into one](doc/Advanced.md#merging-multiple-configuration-files-into-one) - * [Definition and access of simple user-defined task configuration ("taskParameters")](doc/Advanced.md#definition-and-access-of-simple-user-defined-task-configuration-taskparameters) - * [Definition and access of user-defined configuration ("extendedTaskParameters")](doc/Advanced.md#definition-and-access-of-user-defined-configuration-extendedtaskparameters) - * [Definition of new arguments](doc/Advanced.md#definition-of-new-arguments) - * [Plugging the QC to an existing DPL workflow](#plugging-the-qc-to-an-existing-dpl-workflow) - * [Production of QC objects outside this framework](#production-of-qc-objects-outside-this-framework) - * [Multi-node setups](#multi-node-setups) - * [Batch processing](#batch-processing) - * [Moving window](#moving-window) - * [Monitor cycles](#monitor-cycles) - * [Writing a DPL data producer](#writing-a-dpl-data-producer) - * [Custom merging](#custom-merging) - * [Critical, resilient and non-critical tasks](#critical-resilient-and-non-critical-tasks) - * [QC with DPL Analysis](#qc-with-dpl-analysis) - * [Uploading objects to QCDB](#uploading-objects-to-qcdb) - * [Propagating Check results to RCT in Bookkeeping](#propagating-check-results-to-rct-in-bookkeeping) - * [Conversion details](#conversion-details) - * [Solving performance issues](#solving-performance-issues) - * [Dispatcher](#dispatcher) - * [QC Tasks](#qc-tasks-1) - * [Mergers](#mergers) - * [Understanding and reducing memory footprint](#understanding-and-reducing-memory-footprint) - * [Configuration](doc/Configuration.md) - * [Global configuration structure](doc/Configuration.md#global-configuration-structure) - * [Common configuration](doc/Configuration.md#common-configuration) - * [QC Tasks configuration](doc/Configuration.md#qc-tasks-configuration) - * [QC Checks configuration](doc/Configuration.md#qc-checks-configuration) - * [QC Aggregators configuration](doc/Configuration.md#qc-aggregators-configuration) - * [QC Post-processing configuration](doc/Configuration.md#qc-post-processing-configuration) - * [External tasks configuration](doc/Configuration.md#external-tasks-configuration) - * [Miscellaneous](doc/Advanced.md#miscellaneous) - * [Data Sampling monitoring](doc/Advanced.md#data-sampling-monitoring) - * [Monitoring metrics](doc/Advanced.md#monitoring-metrics) - * [Common check IncreasingEntries](doc/Advanced.md#common-check-increasingentries) - * [Update the shmem segment size of a detector](doc/Advanced.md#update-the-shmem-segment-size-of-a-detector) + * [The post-processing framework](doc/PostProcessing#the-post-processing-framework) + * [Post-processing interface](doc/PostProcessing#post-processing-interface) + * [Configuration](doc/PostProcessing#configuration) + * [Running it](doc/PostProcessing#running-it) + * [Convenience classes](doc/PostProcessing#convenience-classes) + * [The TrendingTask class](doc/PostProcessing#the-trendingtask-class) + * [The SliceTrendingTask class](doc/PostProcessing#the-slicetrendingtask-class) + * [The ReferenceComparatorTask class](doc/PostProcessing#the-referencecomparatortask-class) + * [The CcdbInspectorTask class](doc/PostProcessing#the-ccdbinspectortask-class) + * [The QualityTask class](doc/PostProcessing#the-qualitytask-class) + * [The BigScreen class](doc/PostProcessing#the-bigscreen-class) + * [More examples](#more-examples) +* [Configuration reference](doc/Configuration.md) + * [Global configuration structure](doc/Configuration.md#global-configuration-structure) + * [Common configuration](doc/Configuration.md#common-configuration) + * [QC Tasks configuration](doc/Configuration.md#qc-tasks-configuration) + * [QC Checks configuration](doc/Configuration.md#qc-checks-configuration) + * [QC Aggregators configuration](doc/Configuration.md#qc-aggregators-configuration) + * [QC Post-processing configuration](doc/Configuration.md#qc-post-processing-configuration) + * [External tasks configuration](doc/Configuration.md#external-tasks-configuration) + * [Merging multiple configuration files into one](doc/Configuration.md#merging-multiple-configuration-files-into-one) + * [Templating config files](doc/Configuration.md#templating-config-files) + * [Definition and access of simple user-defined task configuration ("taskParameters")](doc/Configuration.md#definition-and-access-of-simple-user-defined-task-configuration-taskparameters) + * [Definition and access of user-defined configuration ("extendedTaskParameters")](doc/Configuration.md#definition-and-access-of-user-defined-configuration-extendedtaskparameters) +* [QCDB and CCDB](doc/QCDB.md) + * [QCDB vs CCDB](doc/QCDB.md#qcdb-vs-ccdb) + * [Details on the data storage format in the QCDB](doc/QCDB.md#details-on-the-data-storage-format-in-the-qcdb) + * [Custom metadata for QC objects in the QCDB](doc/QCDB.md#custom-metadata-for-qc-objects-in-the-qcdb) + * [Instructions to move an object in the QCDB](doc/QCDB.md#instructions-to-move-an-object-in-the-qcdb) + * [Accessing objects in CCDB](doc/QCDB.md#accessing-objects-in-ccdb) + * [Access GRP objects with GRP Geom Helper](doc/QCDB.md#access-grp-objects-with-grp-geom-helper) + * [Global Tracking Data Request helper](doc/QCDB.md#global-tracking-data-request-helper) + * [Local CCDB setup](doc/QCDB.md#local-ccdb-setup) +* [FLP Suite](doc/FLPsuite.md) + * [Developing QC modules on a machine with FLP suite](doc/FLPsuite.md#developing-qc-modules-on-a-machine-with-flp-suite) + * [Switch detector in the workflow readout-dataflow](doc/FLPsuite.md#switch-detector-in-the-workflow-readout-dataflow) + * [Get all the task output to the infologger](doc/FLPsuite.md#get-all-the-task-output-to-the-infologger) + * [Using a different config file with the general QC](doc/FLPsuite.md#using-a-different-config-file-with-the-general-qc) + * [Enable the repo cleaner](doc/FLPsuite.md#enable-the-repo-cleaner) + * [Definition of new arguments](doc/FLPsuite.md#definition-of-new-arguments) + * [Reference data](doc/FLPsuite.md#reference-data) +* [Miscellaneous](doc/Miscellaneous.md) + * [Asynchronous Data and Monte Carlo QC operations](doc/Miscellaneous.md#asynchronous-data-and-monte-carlo-qc-operations) + * [QCG](doc/Miscellaneous.md#qcg) + * [Data Sampling monitoring](doc/Miscellaneous.md#data-sampling-monitoring) + * [Monitoring metrics](doc/Miscellaneous.md#monitoring-metrics) + * [Common check IncreasingEntries](doc/Miscellaneous.md#common-check-increasingentries) + * [Common check TrendCheck](doc/Miscellaneous.md#common-check-trendcheck) + * [Update the shmem segment size of a detector](doc/Miscellaneous.md#update-the-shmem-segment-size-of-a-detector) + * [Readout chain](doc/Miscellaneous.md#readout-chain) + * [Writing a DPL data producer](doc/Miscellaneous.md#writing-a-dpl-data-producer) ### Where to get help diff --git a/doc/Configuration.md b/doc/Configuration.md index dfd8b8e171..830a869cea 100644 --- a/doc/Configuration.md +++ b/doc/Configuration.md @@ -5,7 +5,6 @@ Configuration reference - * [Configuration reference](#configuration-reference) * [Global configuration structure](#global-configuration-structure) * [Common configuration](#common-configuration) * [QC Tasks configuration](#qc-tasks-configuration) diff --git a/doc/FAQ.md b/doc/FAQ.md index d4b153818f..2cd9d87451 100644 --- a/doc/FAQ.md +++ b/doc/FAQ.md @@ -15,8 +15,7 @@ * [Run](#run) * [Why are my QC processes using 100% CPU ?](#why-are-my-qc-processes-using-100-cpu-) * [QCDB](#qcdb) - * [How to see which objects are stored in the CCDB ?](#how-to-see-which-objects-are-stored-in-the-ccdb-) - * [How to delete objects from the CCDB ?](#how-to-delete-objects-from-the-ccdb-) + * [How to see which objects are stored in the QCDB ?](#how-to-see-which-objects-are-stored-in-the-ccdb-) * [My objects are not stored due to their size. What can I do ?](#my-objects-are-not-stored-due-to-their-size-what-can-i-do-) @@ -72,38 +71,12 @@ There are more options in the "Advanced" section of this guide. ## QCDB -### How to see which objects are stored in the CCDB ? +### How to see which objects are stored in the QCDB ? The easiest is to use the QCG (QC GUI). If you use the central test CCDB, you can use the central test QCG. Simply direct your browser to [https://qcg-test.cern.ch](https://qcg-test.cern.ch). If for some reason you don't want or can't use the QCG, the CCDB provides a web interface accessible at [http://ccdb-test.cern.ch:8080/browse/](http://ccdb-test.cern.ch:8080/browse/). -### How to delete objects from the CCDB ? - -#### The nuclear option - -By accessing `http://ccdb-test.cern.ch:8080/truncate/path/to/folder/.*` you will delete all the objects at the given path. Careful with that please ! Don't delete data of others.
    In production it will of course not be possible to do so. - -#### A set of run exported from the logbook - -Use `o2-qc-repo-delete-objects-in-runs`. The `--help` will tell you all you need to know about this tool. -`--print-list` is very useful to see what will be deleted. - -Here is an example: -``` -o2-qc-repo-delete-objects-in-runs --url http://localhost:8083 --path qc/EMC/.* --runs-csv-file /tmp/runs_standalone_bad_LHC22m.csv -``` - -#### By time period - -Use `o2-qc-repo-delete-time-interval`. The `--help` will tell you all you need to know about this tool. -`--print-list` is very useful to see what will be deleted. - -Here is an example: -``` -o2-qc-repo-delete-time-interval --url http://localhost:8083 --path qc_async/EMC/MO/AsyncTrend --from 0 --to 1654706422910 -``` - ### My objects are not stored due to their size. What can I do ? You see warnings in the logs: diff --git a/doc/Framework.md b/doc/Framework.md index 95218a8cfa..8b1e9db2d3 100644 --- a/doc/Framework.md +++ b/doc/Framework.md @@ -5,7 +5,6 @@ Framework - * [Framework](#framework) * [Plugging the QC to an existing DPL workflow](#plugging-the-qc-to-an-existing-dpl-workflow) * [Production of QC objects outside this framework](#production-of-qc-objects-outside-this-framework) * [Multi-node setups](#multi-node-setups) @@ -750,4 +749,4 @@ is setup, one can update the monitoring url in the config file to point to it. --- -[← Go back to Post-Processing](PostProcessing.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to Configuration →](Configuration.md) \ No newline at end of file +[← Go back to Modules development](ModulesDevelopment.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to Configuration →](Configuration.md) \ No newline at end of file diff --git a/doc/Miscellaneous.md b/doc/Miscellaneous.md index 2d29a6a7d1..09c3c8e92b 100644 --- a/doc/Miscellaneous.md +++ b/doc/Miscellaneous.md @@ -14,11 +14,9 @@ Miscellaneous * [Monitoring metrics](#monitoring-metrics) * [Common check IncreasingEntries](#common-check-increasingentries) * [Common check TrendCheck](#common-check-trendcheck) - * [Full configuration example](#full-configuration-example) * [Update the shmem segment size of a detector](#update-the-shmem-segment-size-of-a-detector) - * [Readout chain (optional)](#readout-chain-optional) - * [Getting real data from readout](#getting-real-data-from-readout) - * [Readout data format as received by the Task](#readout-data-format-as-received-by-the-task) +* [Readout chain](#readout-chain) +* [Writing a DPL data producer](#writing-a-dpl-data-producer) @@ -294,14 +292,15 @@ The values are relative to the canvas size, so in the example above the label wi In consul go to `o2/runtime/aliecs/defaults` and modify the file corresponding to the detector: [det]_qc_shm_segment_size -## Readout chain (optional) +# Readout chain -In this second example, we are going to use the Readout as our data source. +In this section we are going to use the Readout as our data source. This is a rare occurence nowadays as detectors have +DPL workflows they can plug QC to. This example assumes that Readout has been compiled beforehand (`aliBuild build Readout --defaults o2`). ![alt text](images/readout-schema.png) -This workflow is a bit different from the basic one. The _Readout_ is not a DPL, nor a FairMQ, device and thus we have to have a _proxy_ to get data from it. This is the extra box going to the _Data Sampling_, which then injects data to the task. This is handled in the _Readout_ as long as you enable the corresponding configuration flag. +This workflow is a bit different. The _Readout_ is not a DPL, nor a FairMQ, device and thus we have to have a _proxy_ to get data from it. This is the extra box going to the _Data Sampling_, which then injects data to the task. This is handled in the _Readout_ as long as you enable the corresponding configuration flag. The first thing is to load the environment for the readout in a new terminal: `alienv enter Readout/latest`. @@ -355,7 +354,7 @@ To change the fraction of the data being monitored, change the option `fraction` "fraction": "0.01", ``` -## Writing a DPL data producer +# Writing a DPL data producer For your convenience, and although it does not lie within the QC scope, we would like to document how to write a simple data producer in the DPL. The DPL documentation can be found [here](https://github.com/AliceO2Group/AliceO2/blob/dev/Framework/Core/README.md) and for questions please head to the [forum](https://alice-talk.web.cern.ch/). diff --git a/doc/ModulesDevelopment.md b/doc/ModulesDevelopment.md index a5a8dbd77c..8f0f1803bf 100644 --- a/doc/ModulesDevelopment.md +++ b/doc/ModulesDevelopment.md @@ -31,8 +31,6 @@ * [A more advanced example](#a-more-advanced-example) -[← Go back to Quickstart](QuickStart.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to Post-processing →](PostProcessing.md) - ## Context Before developing a module, one should have a bare idea of what the QualityControl is and how it is designed. The following sections explore these aspects. @@ -589,4 +587,4 @@ o2-qc-run-advanced --no-qc | o2-qc --config json://${QUALITYCONTROL_ROOT}/etc/ad --- -[← Go back to Quickstart](QuickStart.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to Post-processing →](PostProcessing.md) +[← Go back to Quickstart](QuickStart.md) | [↑ Go to the Table of Content ↑](../README.md) | [Continue to Framework →](Framework.md) From acbbefde7c35128953c23fa8a19940e7b9f82756 Mon Sep 17 00:00:00 2001 From: Barthelemy Date: Mon, 12 May 2025 13:33:51 +0200 Subject: [PATCH 09/10] couple of fixes --- README.md | 2 +- doc/QuickStart.md | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index b8c9411012..82b5218c19 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ For a general overview of our (O2) software, organization and processes, please * [Post-processing example](doc/QuickStart.md#post-processing-example) * [Modules development](doc/ModulesDevelopment.md) * [Context](doc/ModulesDevelopment.md#context) - * [QC architecture](doc/ModulesDevelopment.md#qc-architecture) + * [QC architecture](doc/ModulesDevelopment.md#qc-architecture) * [DPL](doc/ModulesDevelopment.md#dpl) * [Data Sampling](doc/ModulesDevelopment.md#data-sampling) * [Code Organization](doc/ModulesDevelopment.md#code-organization) diff --git a/doc/QuickStart.md b/doc/QuickStart.md index 74fda9be0f..d6a1942ecf 100644 --- a/doc/QuickStart.md +++ b/doc/QuickStart.md @@ -102,7 +102,7 @@ If you click on the item in the tree again, you will see an updated version of t In the example above, the devices are configured in the config file named `basic.json`. It is installed in `$QUALITYCONTROL_ROOT/etc`. Each time you rebuild the code, `$QUALITYCONTROL_ROOT/etc/basic.json` is overwritten by the file in the source directory (`~/alice/QualityControl/Framework/basic.json`). -The configuration for the QC is made of many parameters described in an [advanced section of the documentation](Advanced.md#configuration-files-details). TODO update link +The configuration for the QC is made of many parameters described in an [this chapter of the documentation](Configuration.md). For now we can see below the definition of a task. `moduleName` and `className` specify respectively the library and the class to load and instantiate to do the actual job of the task. ```json @@ -133,8 +133,6 @@ o2-qc-run-postprocessing --config json://${QUALITYCONTROL_ROOT}/etc/postprocessi On the [QCG website](https://qcg-test.cern.ch/?page=objectTree) you will see a TTree and additional plots visible under the path `/qc/TST/MO/ExampleTrend`. They show how different properties of the Example histogram change during time. The longer the applications are running, the more data will be visible. -The post-processing framework and its convenience classes allow to trend and correlate various characteristics of histograms and other data structures generated by QC tasks and checks. One can create their own post-processing tasks or use the ones included in the framework and configure them for one's own needs. - -TODO add a link to the postprocessing chapter +The [post-processing component](doc/PostProcessing.md) and its convenience classes allow to trend and correlate various characteristics of histograms and other data structures generated by QC tasks and checks. One can create their own post-processing tasks or use the ones included in the framework and configure them for one's own needs. [↑ Go to the Table of Content ↑](../README.md) | [Continue to Modules Development →](ModulesDevelopment.md) From ee3f949e2b0bf6890666a05aa91da3ec04399825 Mon Sep 17 00:00:00 2001 From: Barthelemy Date: Thu, 15 May 2025 13:57:54 +0200 Subject: [PATCH 10/10] address PR comments --- README.md | 3 -- doc/DevelopersTips.md | 24 +++++++++++++++ doc/FLPsuite.md | 17 ----------- doc/ModulesDevelopment.md | 62 +-------------------------------------- doc/QCDB.md | 12 ++------ 5 files changed, 28 insertions(+), 90 deletions(-) diff --git a/README.md b/README.md index 82b5218c19..7c8875615b 100644 --- a/README.md +++ b/README.md @@ -41,8 +41,6 @@ For a general overview of our (O2) software, organization and processes, please * [Naming convention](doc/ModulesDevelopment.md#naming-convention) * [Committing code](doc/ModulesDevelopment.md#committing-code) * [Data sources](doc/ModulesDevelopment.md#data-sources) - * [Readout](doc/ModulesDevelopment.md#readout) - * [DPL workflow](doc/ModulesDevelopment.md#dpl-workflow) * [Run number and other run attributes (period, pass type, provenance)](doc/ModulesDevelopment.md#run-number-and-other-run-attributes-period-pass-type-provenance) * [A more advanced example](doc/ModulesDevelopment.md#a-more-advanced-example) * [Framework](doc/Framework.md) @@ -99,7 +97,6 @@ For a general overview of our (O2) software, organization and processes, please * [Get all the task output to the infologger](doc/FLPsuite.md#get-all-the-task-output-to-the-infologger) * [Using a different config file with the general QC](doc/FLPsuite.md#using-a-different-config-file-with-the-general-qc) * [Enable the repo cleaner](doc/FLPsuite.md#enable-the-repo-cleaner) - * [Definition of new arguments](doc/FLPsuite.md#definition-of-new-arguments) * [Reference data](doc/FLPsuite.md#reference-data) * [Miscellaneous](doc/Miscellaneous.md) * [Asynchronous Data and Monte Carlo QC operations](doc/Miscellaneous.md#asynchronous-data-and-monte-carlo-qc-operations) diff --git a/doc/DevelopersTips.md b/doc/DevelopersTips.md index 90c52bbc3d..2109b98a5d 100644 --- a/doc/DevelopersTips.md +++ b/doc/DevelopersTips.md @@ -562,3 +562,27 @@ Just run aliBuild with following parameters from the folder with prepared alidis ``` aliBuild build QualityControl --defaults o2 --docker --architecture slc8_x86-64 ``` + +## Instructions to move an object in the QCDB + +The script `o2-qc-repo-move-objects` lets the user move an object, and thus all the versions attached to it. E.g.: + +``` +python3 o2-qc-repo-move-objects --url http://ccdb-test.cern.ch:8080 --path qc/TST/MO/Bob --new-path qc/TST/MO/Bob2 --log-level 10 +``` + +## Definition of new arguments + +One can also tell the DPL driver to accept new arguments. This is done using the `customize` method at the top of your workflow definition (usually called "runXXX" in the QC). + +For example, to add two parameters of different types do : + +``` +void customize(std::vector& workflowOptions) +{ + workflowOptions.push_back( + ConfigParamSpec{ "config-path", VariantType::String, "", { "Path to the config file. Overwrite the default paths. Do not use with no-data-sampling." } }); + workflowOptions.push_back( + ConfigParamSpec{ "no-data-sampling", VariantType::Bool, false, { "Skips data sampling, connects directly the task to the producer." } }); +} +``` \ No newline at end of file diff --git a/doc/FLPsuite.md b/doc/FLPsuite.md index 1a7525de83..169887fde3 100644 --- a/doc/FLPsuite.md +++ b/doc/FLPsuite.md @@ -10,7 +10,6 @@ FLP Suite * [Get all the task output to the infologger](#get-all-the-task-output-to-the-infologger) * [Using a different config file with the general QC](#using-a-different-config-file-with-the-general-qc) * [Enable the repo cleaner](#enable-the-repo-cleaner) - * [Definition of new arguments](#definition-of-new-arguments) * [Reference data](#reference-data) @@ -114,22 +113,6 @@ By defaults there is a _disabled_ cron job : 3. update the cron job to use the modified config file 4. uncomment the cron job -## Definition of new arguments - -One can also tell the DPL driver to accept new arguments. This is done using the `customize` method at the top of your workflow definition (usually called "runXXX" in the QC). - -For example, to add two parameters of different types do : - -``` -void customize(std::vector& workflowOptions) -{ - workflowOptions.push_back( - ConfigParamSpec{ "config-path", VariantType::String, "", { "Path to the config file. Overwrite the default paths. Do not use with no-data-sampling." } }); - workflowOptions.push_back( - ConfigParamSpec{ "no-data-sampling", VariantType::Bool, false, { "Skips data sampling, connects directly the task to the producer." } }); -} -``` - ## Reference data A reference object is an object from a previous run. It is usually used as a point of comparison. diff --git a/doc/ModulesDevelopment.md b/doc/ModulesDevelopment.md index 8f0f1803bf..77b9315acb 100644 --- a/doc/ModulesDevelopment.md +++ b/doc/ModulesDevelopment.md @@ -25,8 +25,6 @@ * [Naming convention](#naming-convention) * [Committing code](#committing-code) * [Data sources](#data-sources) - * [Readout](#readout) - * [DPL workflow](#dpl-workflow) * [Run number and other run attributes (period, pass type, provenance)](#run-number-and-other-run-attributes-period-pass-type-provenance) * [A more advanced example](#a-more-advanced-example) @@ -450,65 +448,7 @@ General ALICE Git guidelines can be accessed [here](https://alisw.github.io/git- ## Data sources -In the final system, the qc gets real data from the DPL devices or the readout processes. During development a number of possibilities are available for the detector teams to develop their QC. We list them below. - -### Readout - -When connecting the QC directly to the readout using the `o2-qc-run-readout` proxy, remember to add this consumer to the config file of the readout and to enable it: -```json -[consumer-fmq-qc] -consumerType=FairMQChannel -enableRawFormat=1 -fmq-name=readout-qc -fmq-address=ipc:///tmp/readout-pipe-1 -fmq-type=pub -fmq-transport=zeromq -unmanagedMemorySize=2G -memoryPoolNumberOfPages=500 -memoryPoolPageSize=1M -enabled=1 -``` - -__Random data__ - -Add one or several dummy equipments: -``` -[equipment-dummy-1] -name=dummy-1 -equipmentType=dummy -enabled=1 -eventMaxSize=200 -eventMinSize=100 -memoryPoolNumberOfPages=100 -memoryPoolPageSize=128k -fillData=1 -``` - -__Live detector data__ - -If a part or the whole detector is ready and connected to 1 or several CRUs in the FLP, configure the readout to get data from there. The exact configuration items should be discussed with the readout experts. - -This is the most realistic test one can do but it is also not very practical as you have to control the data taking and be on the machine equipped with a CRU. See the next section to alleviate this situation. - -__Detector data file__ - -To record a data file with readout while getting data from a CRU, add the following piece to the readout configuration file: -``` -[consumer-rec] -enabled=1 -consumerType=fileRecorder -fileName=/tmp/dataRecorder_flp1.raw -``` - -To read it back with readout, for instance on another machine, add: -``` -[equipment-player-1] -equipmentType=player -memoryPoolPageSize=1M -memoryPoolNumberOfPages=1000 -filePath=/path/to/dataRecorder_flp1.raw -autoChunk=1 -``` +In the final system, the qc gets real data from the DPL devices or the readout processes. During development a number of possibilities are available for the detector teams to develop their QC. We list them below. ### DPL workflow diff --git a/doc/QCDB.md b/doc/QCDB.md index 21f9a93c3d..f64479b80a 100644 --- a/doc/QCDB.md +++ b/doc/QCDB.md @@ -33,13 +33,15 @@ The detector name and the taskname are set in the config file : ```json "tasks": { - "QcTask": { <---------- task name + "QcTask": { <---------- task ID "active": "true", + "taskName": "QcTask", <--------- task name "className": "o2::quality_control_modules::skeleton::SkeletonTask", "moduleName": "QcSkeleton", "detectorName": "TST", <---------- detector name ``` +If the task name is not specified then we use the task ID. The quality is stored as a CCDB metadata of the object. ## Custom metadata for QC objects in the QCDB @@ -61,14 +63,6 @@ It is also possible to add or update metadata of a MonitorObject directly: mo->addOrUpdateMetadata(key, value); ``` -## Instructions to move an object in the QCDB - -The script `o2-qc-repo-move-objects` lets the user move an object, and thus all the versions attached to it. E.g.: - -``` -python3 o2-qc-repo-move-objects --url http://ccdb-test.cern.ch:8080 --path qc/TST/MO/Bob --new-path qc/TST/MO/Bob2 --log-level 10 -``` - ## Accessing objects in CCDB The recommended way (excluding postprocessing) to access the run conditions in the _CCDB_ is to use a `Lifetime::Condition` DPL input, which can be requested as in the query below: