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