Skip to content

Commit b0f0828

Browse files
dstoccoalcaliva
authored andcommitted
Macro to build the rejectlist for MID (#13330)
* Macro to build the rejectlist for MID * Add instruction on how to use the macros
1 parent 000bdc6 commit b0f0828

File tree

3 files changed

+354
-1
lines changed

3 files changed

+354
-1
lines changed

Detectors/MUON/MID/Calibration/macros/CMakeLists.txt

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,10 @@
1111

1212
o2_add_test_root_macro(
1313
ccdbUtils.C
14-
PUBLIC_LINK_LIBRARIES O2::MIDCalibration O2::MathUtils
14+
PUBLIC_LINK_LIBRARIES O2::MIDFiltering O2::DataFormatsMID O2::CCDB
15+
LABELS muon mid)
16+
17+
o2_add_test_root_macro(
18+
build_rejectlist.C
19+
PUBLIC_LINK_LIBRARIES O2::MIDFiltering O2::MIDGlobalMapping O2::DataFormatsMID O2::CCDB O2::CommonUtils
1520
LABELS muon mid)
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
<!-- doxy
2+
\page refMUONMIDCalibrationMacros MID Calibration macros
3+
/doxy -->
4+
5+
# MID calibration macros
6+
7+
This repository contains macros that can be used to handle calibration objects in the CCDB.
8+
9+
## ccdbUtils.C
10+
11+
This macro allows to query a series of MID CCDB objects in the CCDB as well as produce default objects.
12+
The basic usage is the following:
13+
14+
Query the list of bad channels from the official CCDB:
15+
16+
```shell
17+
root -l
18+
.x ccdbUtils.C("querybad",1721252719000,"mask.txt",true,"http://alice-ccdb.cern.ch")
19+
```
20+
21+
Upload the default list of fake dead channels to the local CCDB (assuming that an instance of the local CCDB is running):
22+
23+
```shell
24+
root -l
25+
.x ccdbUtils.C("uploadfake",1,"mask.txt",true,"localhost:8080")
26+
```
27+
28+
The macro is also used to keep track of the fake dead channels, which are generated in `makeFakeDeadChannels()`.
29+
30+
## buils_rejectList.C
31+
32+
This macro analyses the Quality flag and the Occupancy plot in the QCCDB and searches for issues appearing in the middle of the run, e.g. local board(s) that become noisy and are then excluded from the data taking by the user logic of the CRU.
33+
It then correlate this information with the GRPECS object in the CCDB in order to create a reject list that will allow to mask the faulty local board(s) from slightly before the problem appeared till the end of the run.
34+
35+
Notice that the QCDB is not directly reachable from outside CERN. In that case one needs to first create an ssh tunnel:
36+
37+
```shell
38+
ssh -L 8083:ali-qcdb-gpn.cern.ch:8083 lxtunnel.cern.ch
39+
```
40+
41+
We also advice to have an instance of the local CCDB running, so that the reject list objects will be saved there.
42+
One can then scan for issues with:
43+
44+
```shell
45+
root -l
46+
build_rejectlist.C+(1716436103391,1721272208000,"localhost:8083")
47+
```
48+
49+
Where the first number is the start timestamp for the scan, and the second is the end timestamp of the scan.
50+
For each problem found, the macro will ask if one wants to upload the reject list to the (local) CCDB or not.
Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
// Copyright 2019-2020 CERN and copyright holders of ALICE O2.
2+
// See https://alice-o2.web.cern.ch/copyright for details of the copyright holders.
3+
// All rights not expressly granted are reserved.
4+
//
5+
// This software is distributed under the terms of the GNU General Public
6+
// License v3 (GPL Version 3), copied verbatim in the file "COPYING".
7+
//
8+
// In applying this license CERN does not waive the privileges and immunities
9+
// granted to it by virtue of its status as an Intergovernmental Organization
10+
// or submit itself to any jurisdiction.
11+
12+
/// \file MID/Calibration/macros/build_rejectlist.C
13+
/// \brief Analyse QC output and build reject list
14+
/// \author Diego Stocco <Diego.Stocco at cern.ch>
15+
/// \date 26 July 2024
16+
17+
#include <string>
18+
#include <iostream>
19+
#include <sstream>
20+
#include <map>
21+
#include <vector>
22+
#include <limits>
23+
#include <algorithm>
24+
#include "TCanvas.h"
25+
#include "TH1.h"
26+
#include "TGraph.h"
27+
#include "TTimeStamp.h"
28+
#include "CCDB/CcdbApi.h"
29+
#include "DataFormatsParameters/GRPECSObject.h"
30+
#include "DetectorsCommonDataFormats/DetID.h"
31+
#include "DataFormatsMID/ColumnData.h"
32+
#include "MIDGlobalMapping/ExtendedMappingInfo.h"
33+
#include "MIDGlobalMapping/GlobalMapper.h"
34+
#include "MIDFiltering/ChannelMasksHandler.h"
35+
#if !defined(__CLING__) || defined(__ROOTCLING__)
36+
#include "CCDB/BasicCCDBManager.h"
37+
#endif
38+
39+
static const std::string sPathQCQuality = "qc/MID/MO/MIDQuality/Trends/global/MIDQuality/MIDQuality";
40+
41+
/// @brief Get timestamp in milliseconds
42+
/// @param timestamp Input timestamp (in s or ms)
43+
/// @return Timestamp in ms
44+
long getTSMS(long timestamp)
45+
{
46+
if (timestamp < 1000000000000) {
47+
return timestamp * 1000;
48+
}
49+
return timestamp;
50+
}
51+
52+
/// @brief Get timestamp in seconds
53+
/// @param timestamp Input timestamp (in s or ms)
54+
/// @return Timestamp in s
55+
long getTSS(long timestamp)
56+
{
57+
if (timestamp < 1000000000000) {
58+
return timestamp;
59+
}
60+
return timestamp / 1000;
61+
}
62+
63+
/// @brief Converts timestamp to human-readable string
64+
/// @param timestamp Timestamp as long
65+
/// @return Timestamp as human-readable string
66+
std::string tsToString(long timestamp)
67+
{
68+
return TTimeStamp(getTSS(timestamp)).AsString("l");
69+
}
70+
71+
/// @brief Converts time range in a human-readable string
72+
/// @param start Start time
73+
/// @param end End time
74+
/// @return Time range as human-readable string
75+
std::string timeRangeToString(long start, long end)
76+
{
77+
std::stringstream ss;
78+
ss << start << " - " << end << " (" << tsToString(start) << " - " << tsToString(end) << ")";
79+
return ss.str();
80+
}
81+
82+
/// @brief Query the CDB path and search for initial validity of objects
83+
/// @param start Query objects created not before
84+
/// @param end Query objects created not after
85+
/// @param api CDB api
86+
/// @param path CDB path
87+
/// @return Vector of start validity of each object sorted in ascending way
88+
std::vector<long> findObjectsTSInPeriod(long start, long end, const o2::ccdb::CcdbApi& api, const char* path)
89+
{
90+
std::vector<long> ts;
91+
auto out = api.list(path, false, "text/plain", getTSMS(end), getTSMS(start));
92+
std::stringstream ss(out);
93+
std::string token;
94+
while (ss >> token) {
95+
if (token.find("Validity") != std::string::npos) {
96+
ss >> token;
97+
ts.emplace_back(std::atol(token.c_str()));
98+
}
99+
}
100+
ts.erase(std::unique(ts.begin(), ts.end()), ts.end());
101+
// Sort timestamps in ascending order
102+
std::sort(ts.begin(), ts.end());
103+
return ts;
104+
}
105+
106+
/// @brief Find the first and last time when the quality was good or bad
107+
/// @param qcQuality MID QC quality canvas
108+
/// @param selectBad When true select bad quality
109+
/// @return Pair with first and last time
110+
std::pair<uint64_t, uint64_t> findTSRange(TCanvas* qcQuality, bool selectBad = true)
111+
{
112+
auto* gr = static_cast<TGraph*>(qcQuality->GetListOfPrimitives()->FindObject("Graph"));
113+
double xp, yp;
114+
std::pair<uint64_t, uint64_t> range{std::numeric_limits<uint64_t>::max(), 0};
115+
for (int ip = 0; ip < gr->GetN(); ++ip) {
116+
gr->GetPoint(ip, xp, yp);
117+
if (selectBad == (yp > 1 && yp < 3)) {
118+
uint64_t xpi = static_cast<uint64_t>(1000 * xp);
119+
range.first = std::min(range.first, xpi);
120+
range.second = std::max(range.second, xpi);
121+
}
122+
}
123+
if (range.first == std::numeric_limits<uint64_t>::max()) {
124+
range.first = 0;
125+
}
126+
return range;
127+
}
128+
129+
/// @brief Find bad channels from the occupancy histograms
130+
/// @param hits Occupancy histogram
131+
/// @param infos Mapping
132+
/// @return Vector of bad channels
133+
std::vector<o2::mid::ColumnData> findBadChannels(TH1* hits, std::vector<o2::mid::ExtendedMappingInfo>& infos)
134+
{
135+
std::map<uint16_t, o2::mid::ColumnData> badChannelsMap;
136+
for (int ibin = 1; ibin <= hits->GetNbinsX(); ++ibin) {
137+
if (hits->GetBinContent(ibin) == 0) {
138+
auto info = infos[ibin - 1];
139+
auto uniqueId = o2::mid::getColumnDataUniqueId(info.deId, info.columnId);
140+
o2::mid::ColumnData col;
141+
col.deId = info.deId;
142+
col.columnId = info.columnId;
143+
auto result = badChannelsMap.emplace(uniqueId, col);
144+
result.first->second.addStrip(info.stripId, info.cathode, info.lineId);
145+
}
146+
}
147+
148+
std::vector<o2::mid::ColumnData> badChannels;
149+
for (auto& item : badChannelsMap) {
150+
badChannels.emplace_back(item.second);
151+
}
152+
return badChannels;
153+
}
154+
155+
/// @brief Converts the bad channels from the occupancy into a reject list (removing the ones from CCDB)
156+
/// @param badChannels Bad channels from the analysis of the occupancy histogram
157+
/// @param badChannelsCCDB Bad channels in the CCDB
158+
/// @return Reject list
159+
std::vector<o2::mid::ColumnData> getRejectList(std::vector<o2::mid::ColumnData> badChannels, const std::vector<o2::mid::ColumnData>& badChannelsCCDB)
160+
{
161+
o2::mid::ChannelMasksHandler mh;
162+
mh.switchOffChannels(badChannelsCCDB);
163+
for (auto& bad : badChannels) {
164+
mh.applyMask(bad);
165+
}
166+
badChannels.erase(std::remove_if(badChannels.begin(), badChannels.end(), [](const o2::mid::ColumnData col) { return col.isEmpty(); }),
167+
badChannels.end());
168+
return badChannels;
169+
}
170+
171+
/// @brief Builds the reject list for the selected timestamp
172+
/// @param timestamp Timestamp for query
173+
/// @param qcdbApi QCDB api
174+
/// @param ccdbApi CCDB api
175+
/// @param outCCDBApi api of the CCDB where the reject list will be uploaded
176+
/// @return Reject list
177+
std::vector<o2::mid::ColumnData> build_rejectlist(long timestamp, const o2::ccdb::CcdbApi& qcdbApi, const o2::ccdb::CcdbApi& ccdbApi, const o2::ccdb::CcdbApi& outCCDBApi)
178+
{
179+
std::map<std::string, std::string> metadata;
180+
auto* qcQuality = qcdbApi.retrieveFromTFileAny<TCanvas>(sPathQCQuality, metadata, getTSMS(timestamp));
181+
if (!qcQuality) {
182+
std::cerr << "Cannot find QC quality for " << tsToString(timestamp) << std::endl;
183+
return {};
184+
}
185+
// Find the first and last timestamp where the quality was bad (if any)
186+
auto badTSRange = findTSRange(qcQuality);
187+
if (badTSRange.second == 0) {
188+
std::cout << "All good" << std::endl;
189+
return {};
190+
}
191+
// Search for the last timestamp for which the run quality was good
192+
auto goodTSRange = findTSRange(qcQuality, false);
193+
// Query the CCDB to see to which run the timestamp corresponds
194+
auto oldestTSInQCQuality = (goodTSRange.first == 0) ? badTSRange.first : goodTSRange.first;
195+
auto grpecs = *ccdbApi.retrieveFromTFileAny<o2::parameters::GRPECSObject>("GLO/Config/GRPECS", metadata, getTSMS(oldestTSInQCQuality));
196+
if (!grpecs.isDetReadOut(o2::detectors::DetID::MID)) {
197+
std::cout << "Error: we are probably reading a parallel run" << std::endl;
198+
grpecs.print();
199+
return {};
200+
}
201+
if (grpecs.getRunType() != o2::parameters::GRPECS::PHYSICS) {
202+
std::cout << "This is not a physics run: skip" << std::endl;
203+
grpecs.print();
204+
return {};
205+
}
206+
207+
auto runRange = o2::ccdb::BasicCCDBManager::getRunDuration(ccdbApi, grpecs.getRun());
208+
long margin = 120000; // Add a two minutes safety margin
209+
runRange.first -= margin; // Subtract margin
210+
runRange.second += margin; // Add margin
211+
212+
// Search for hits histogram in the period where the QC quality was bad
213+
auto tsVector = findObjectsTSInPeriod(badTSRange.first, badTSRange.second, qcdbApi, "qc/MID/MO/QcTaskMIDDigits/Hits");
214+
if (tsVector.empty()) {
215+
std::cerr << "Cannot find hits in period " << tsToString(badTSRange.first) << " - " << tsToString(badTSRange.second) << std::endl;
216+
return {};
217+
}
218+
// Focus on the first object found
219+
TH1* occupancy = qcdbApi.retrieveFromTFileAny<TH1F>("qc/MID/MO/QcTaskMIDDigits/Hits", metadata, getTSMS(tsVector.front()));
220+
o2::mid::GlobalMapper gm;
221+
auto infos = gm.buildStripsInfo();
222+
auto badChannels = findBadChannels(occupancy, infos);
223+
auto badChannelsCCDB = *ccdbApi.retrieveFromTFileAny<std::vector<o2::mid::ColumnData>>("MID/Calib/BadChannels", metadata, getTSMS(timestamp));
224+
auto rejectList = getRejectList(badChannels, badChannelsCCDB);
225+
if (rejectList.empty()) {
226+
std::cout << "Warning: reject list was empty. It probably means that an entire board is already masked in calibration for run " << grpecs.getRun() << std::endl;
227+
return {};
228+
}
229+
230+
// Print some useful information
231+
std::cout << "Reject list:" << std::endl;
232+
for (auto& col : rejectList) {
233+
std::cout << col << std::endl;
234+
}
235+
std::cout << "Run number: " << grpecs.getRun() << std::endl;
236+
std::cout << "SOR - EOR: " << timeRangeToString(grpecs.getTimeStart(), grpecs.getTimeEnd()) << std::endl;
237+
std::cout << "SOT - EOT: " << timeRangeToString(runRange.first, runRange.second) << std::endl;
238+
std::cout << "Good: " << timeRangeToString(goodTSRange.first, goodTSRange.second) << std::endl;
239+
std::cout << "Bad: " << timeRangeToString(badTSRange.first, badTSRange.second) << std::endl;
240+
241+
// Set the start of the reject list to the last timestamp in which the occupancy was ok
242+
auto startRL = goodTSRange.second;
243+
if (goodTSRange.first == 0) {
244+
// If the quality was bad for the full run, set the start of the reject list to the SOR
245+
std::cout << "CAVEAT: no good TS found. Will use SOT instead" << std::endl;
246+
startRL = runRange.first;
247+
}
248+
// Set the end of the reject list to the end of run
249+
auto endRL = runRange.second;
250+
// Ask if you want to upload the object to the CCDB
251+
std::cout << "Upload reject list with validity: " << startRL << " - " << endRL << " to " << outCCDBApi.getURL() << "? [y/n]" << std::endl;
252+
std::string answer;
253+
std::cin >> answer;
254+
if (answer == "y") {
255+
std::cout << "Storing RejectList valid from " << startRL << " to " << endRL << std::endl;
256+
outCCDBApi.storeAsTFileAny(&rejectList, "MID/Calib/RejectList", metadata, startRL, endRL);
257+
}
258+
return rejectList;
259+
}
260+
261+
/// @brief Builds the reject list for the selected timestamp
262+
/// @param timestamp Timestamp for query
263+
/// @param qcdbUrl QCDB URL
264+
/// @param ccdbUrl CCDB URL
265+
/// @param outCCDBUrl URL of the CCDB where the reject list will be uploaded
266+
/// @return Reject list
267+
std::vector<o2::mid::ColumnData> build_rejectlist(long timestamp, const char* qcdbUrl = "http://ali-qcdb-gpn.cern.ch:8083", const char* ccdbUrl = "http://alice-ccdb.cern.ch", const char* outCCDBUrl = "http://localhost:8080")
268+
{
269+
// Get the QC quality object for the selected timestamp
270+
o2::ccdb::CcdbApi qcdbApi;
271+
qcdbApi.init(qcdbUrl);
272+
o2::ccdb::CcdbApi ccdbApi;
273+
ccdbApi.init(ccdbUrl);
274+
o2::ccdb::CcdbApi outCCDBApi;
275+
outCCDBApi.init(outCCDBUrl);
276+
return build_rejectlist(timestamp, qcdbApi, ccdbApi, outCCDBApi);
277+
}
278+
279+
/// @brief Builds the reject list iin a time range
280+
/// @param start Start time for query
281+
/// @param end End time for query
282+
/// @param qcdbUrl QCDB URL
283+
/// @param ccdbUrl CCDB URL
284+
/// @param outCCDBUrl URL of the CCDB where the reject lists will be uploaded
285+
void build_rejectlist(long start, long end, const char* qcdbUrl = "http://ali-qcdb-gpn.cern.ch:8083", const char* ccdbUrl = "http://alice-ccdb.cern.ch", const char* outCCDBUrl = "http://localhost:8080")
286+
{
287+
// Query the MID QC quality objects
288+
o2::ccdb::CcdbApi qcdbApi;
289+
qcdbApi.init(qcdbUrl);
290+
o2::ccdb::CcdbApi ccdbApi;
291+
ccdbApi.init(ccdbUrl);
292+
o2::ccdb::CcdbApi outCCDBApi;
293+
outCCDBApi.init(outCCDBUrl);
294+
auto objectsTS = findObjectsTSInPeriod(start, end, qcdbApi, sPathQCQuality.c_str());
295+
for (auto ts : objectsTS) {
296+
build_rejectlist(ts, qcdbApi, ccdbApi, outCCDBApi);
297+
}
298+
}

0 commit comments

Comments
 (0)