CMS 3D CMS Logo

QualityTester.cc
Go to the documentation of this file.
1 /*
2  * \file QualityTester.cc
3  *
4  * Harvesting module that applies Quality Tests to MonitorElements.
5  * Which tests are run an be configured using an XML-based configuration.
6  * \author Marcel Schneider
7  * based on M. Zanetti's older module.
8  *
9  */
10 
19 
22 
23 #include <cmath>
24 #include <memory>
25 #include <string>
26 #include <cstdio>
27 #include <sstream>
28 #include <iostream>
29 
30 #include <boost/property_tree/xml_parser.hpp>
31 #include <boost/property_tree/ptree.hpp>
32 
33 #include <fnmatch.h>
34 
35 class QualityTester : public DQMEDHarvester {
36 public:
39 
41  ~QualityTester() override;
42 
43 protected:
48  edm::LuminosityBlock const& lumiSeg,
49  edm::EventSetup const& c) override;
50  void dqmEndRun(DQMStore::IBooker&, DQMStore::IGetter&, const edm::Run& r, const edm::EventSetup& c) override;
52 
53 private:
54  void performTests(DQMStore::IGetter& igetter);
55 
56  int nEvents;
65  bool verboseQT;
66 
67  // this vector holds and owns all the QTest objects. mostly for memory management.
68  std::vector<std::unique_ptr<QCriterion>> qtestobjects;
69  // we use this structure to check which tests to run on which MEs. The first
70  // part is the pattern with wildcards matching MEs that the test should be
71  // applied to, the second points to an object in qtestobjects. We cannot own
72  // the objects here since more than one pattern can use the same test.
73  std::vector<std::pair<std::string, QCriterion*>> qtestpatterns;
74 
75  void configureTests(std::string const& file);
76  std::unique_ptr<QCriterion> makeQCriterion(boost::property_tree::ptree const& config);
77 };
78 
80  prescaleFactor = ps.getUntrackedParameter<int>("prescaleFactor", 1);
81  getQualityTestsFromFile = ps.getUntrackedParameter<bool>("getQualityTestsFromFile", true);
82  Label = ps.getUntrackedParameter<std::string>("label", "");
83  reportThreshold = ps.getUntrackedParameter<std::string>("reportThreshold", "");
84  testInEventloop = ps.getUntrackedParameter<bool>("testInEventloop", false);
85  qtestOnEndRun = ps.getUntrackedParameter<bool>("qtestOnEndRun", true);
86  qtestOnEndJob = ps.getUntrackedParameter<bool>("qtestOnEndJob", false);
87  qtestOnEndLumi = ps.getUntrackedParameter<bool>("qtestOnEndLumi", false);
88  verboseQT = ps.getUntrackedParameter<bool>("verboseQT", true);
89 
92  configureTests(qtlist.fullPath());
93  } else {
94  assert(!"Reading from DB no longer supported.");
95  }
96 
97  nEvents = 0;
98 }
99 
101 
103  DQMStore::IGetter& igetter,
104  const edm::Event& e,
105  const edm::EventSetup& c) {
106  if (testInEventloop) {
107  nEvents++;
108  if (prescaleFactor > 0 && nEvents % prescaleFactor == 0) {
109  performTests(igetter);
110  }
111  }
112 }
113 
115  DQMStore::IGetter& igetter,
116  edm::LuminosityBlock const& lumiSeg,
117  edm::EventSetup const& context) {
119  if (prescaleFactor > 0 && lumiSeg.id().luminosityBlock() % prescaleFactor == 0) {
120  performTests(igetter);
121  }
122  }
123 }
124 
126  DQMStore::IGetter& igetter,
127  edm::Run const& run,
128  edm::EventSetup const& context) {
129  if (qtestOnEndRun)
130  performTests(igetter);
131 }
132 
134  if (qtestOnEndJob)
135  performTests(igetter);
136 }
137 
139  edm::LogVerbatim("QualityTester") << "Running the Quality Test";
140 
141  auto mes = igetter.getAllContents("");
142 
143  for (auto me : mes) {
144  std::string name = me->getFullname();
145  for (auto& kv : this->qtestpatterns) {
146  std::string& pattern = kv.first;
147  QCriterion* qtest = kv.second;
148  int match = fnmatch(pattern.c_str(), name.c_str(), 0);
149  if (match == FNM_NOMATCH)
150  continue;
151  if (match != 0)
152  throw cms::Exception("QualityTester")
153  << "Something went wrong with fnmatch: pattern = '" << pattern << "' and string = '" << name << "'";
154 
155  // name matched, apply test.
156  // Using the classic ME API for now.
157  QReport* qr;
158  DQMNet::QValue* qv;
159  me->getQReport(/* create */ true, qtest->getName(), qr, qv);
160  assert(qtest); // null might be valid, maybe replace with if
161  assert(qr);
162  assert(qv);
163  qtest->runTest(me, *qr, *qv);
164  // this propagates the result into the DQMNet object flags
165  me->syncCoreObject();
166  }
167  }
168 
169  if (!reportThreshold.empty()) {
170  // map {red, orange, black} -> [QReport message, ...]
171  std::map<std::string, std::vector<std::string>> theAlarms;
172  // populate from MEs hasError, hasWarning, hasOther
173  for (auto me : mes) {
174  // TODO: This logic is rather broken and suppresses errors when there
175  // are warnings (or suppresses both when there are others. But this is
176  // how it always was, and we keep it for now for compatibility.
177  std::vector<QReport*> report;
178  std::string colour;
179  if (me->hasError()) {
180  colour = "red";
181  report = me->getQErrors();
182  }
183  if (me->hasWarning()) {
184  colour = "orange";
185  report = me->getQWarnings();
186  }
187  if (me->hasOtherReport()) {
188  colour = "black";
189  report = me->getQOthers();
190  }
191  for (auto r : report) {
192  theAlarms[colour].push_back(r->getMessage());
193  }
194  }
195 
196  // writes to stdout, because it alyways wrote to stdout.
197  for (auto& theAlarm : theAlarms) {
198  const std::string& alarmType = theAlarm.first;
199  const std::vector<std::string>& msgs = theAlarm.second;
200  if ((reportThreshold == "black") ||
201  (reportThreshold == "orange" && (alarmType == "orange" || alarmType == "red")) ||
202  (reportThreshold == "red" && alarmType == "red")) {
203  std::cout << std::endl;
204  std::cout << "Error Type: " << alarmType << std::endl;
205  for (auto const& msg : msgs)
206  std::cout << msg << std::endl;
207  }
208  }
209  std::cout << std::endl;
210  }
211 }
212 
213 std::unique_ptr<QCriterion> QualityTester::makeQCriterion(boost::property_tree::ptree const& config) {
214  // For whatever reasons the compiler needs the "template" keyword on ptree::get.
215  // To save some noise in the code we have the preprocessor add it everywhere.
216 #define get template get
217  // TODO: make the parameter names more consistent.
218  // TODO: add defaults where it makes sense.
220  std::function<std::unique_ptr<QCriterion>(boost::property_tree::ptree const&, std::string& name)>>
221  qtestmakers = {{CheckVariance::getAlgoName(),
222  [](auto const& config, std::string& name) {
223  auto test = std::make_unique<CheckVariance>(name);
224  test->setWarningProb(config.get<float>("warning", QCriterion::WARNING_PROB_THRESHOLD));
225  test->setErrorProb(config.get<float>("error", QCriterion::ERROR_PROB_THRESHOLD));
226  return test;
227  }},
229  [](auto const& config, std::string& name) {
230  auto test = std::make_unique<CompareLastFilledBin>(name);
231  test->setWarningProb(config.get<float>("warning", QCriterion::WARNING_PROB_THRESHOLD));
232  test->setErrorProb(config.get<float>("error", QCriterion::ERROR_PROB_THRESHOLD));
233  test->setAverage(config.get<float>("AvVal"));
234  test->setMin(config.get<float>("MinVal"));
235  test->setMax(config.get<float>("MaxVal"));
236  return test;
237  }},
239  [](auto const& config, std::string& name) {
240  auto test = std::make_unique<CompareToMedian>(name);
241  test->setWarningProb(config.get<float>("warning", QCriterion::WARNING_PROB_THRESHOLD));
242  test->setErrorProb(config.get<float>("error", QCriterion::ERROR_PROB_THRESHOLD));
243  test->setMin(config.get<float>("MinRel"));
244  test->setMax(config.get<float>("MaxRel"));
245  test->setMinMedian(config.get<float>("MinAbs"));
246  test->setMaxMedian(config.get<float>("MaxAbs"));
247  test->setEmptyBins(config.get<int>("UseEmptyBins"));
248  test->setStatCut(config.get<float>("StatCut", 0));
249  return test;
250  }},
252  [](auto const& config, std::string& name) {
253  auto test = std::make_unique<ContentSigma>(name);
254  test->setWarningProb(config.get<float>("warning", QCriterion::WARNING_PROB_THRESHOLD));
255  test->setErrorProb(config.get<float>("error", QCriterion::ERROR_PROB_THRESHOLD));
256  test->setNumXblocks(config.get<int>("Xblocks"));
257  test->setNumYblocks(config.get<int>("Yblocks"));
258  test->setNumNeighborsX(config.get<int>("neighboursX"));
259  test->setNumNeighborsY(config.get<int>("neighboursY"));
260  test->setToleranceNoisy(config.get<double>("toleranceNoisy"));
261  test->setToleranceDead(config.get<double>("toleranceDead"));
262  test->setNoisy(config.get<double>("noisy"));
263  test->setDead(config.get<double>("dead"));
264  test->setXMin(config.get<int>("xMin"));
265  test->setXMax(config.get<int>("xMax"));
266  test->setYMin(config.get<int>("yMin"));
267  test->setYMax(config.get<int>("yMax"));
268  return test;
269  }},
271  [](auto const& config, std::string& name) {
272  auto test = std::make_unique<ContentsWithinExpected>(name);
273  test->setWarningProb(config.get<float>("warning", QCriterion::WARNING_PROB_THRESHOLD));
274  test->setErrorProb(config.get<float>("error", QCriterion::ERROR_PROB_THRESHOLD));
275  test->setUseEmptyBins(config.get<bool>("useEmptyBins"));
276  test->setMinimumEntries(config.get<int>("minEntries"));
277  test->setMeanTolerance(config.get<float>("toleranceMean"));
278  if (config.get<double>("minMean", 0) || config.get<double>("maxMean", 0))
279  test->setMeanRange(config.get<double>("minMean"), config.get<double>("maxMean"));
280  if (config.get<double>("minRMS", 0) || config.get<double>("maxRMS", 0))
281  test->setRMSRange(config.get<double>("minRMS"), config.get<double>("maxRMS"));
282  return test;
283  }},
285  [](auto const& config, std::string& name) {
286  auto test = std::make_unique<ContentsXRange>(name);
287  test->setWarningProb(config.get<float>("warning", QCriterion::WARNING_PROB_THRESHOLD));
288  test->setErrorProb(config.get<float>("error", QCriterion::ERROR_PROB_THRESHOLD));
289  test->setAllowedXRange(config.get<double>("xmin"), config.get<double>("xmax"));
290  return test;
291  }},
293  [](auto const& config, std::string& name) {
294  auto test = std::make_unique<ContentsYRange>(name);
295  test->setWarningProb(config.get<float>("warning", QCriterion::WARNING_PROB_THRESHOLD));
296  test->setErrorProb(config.get<float>("error", QCriterion::ERROR_PROB_THRESHOLD));
297  test->setAllowedYRange(config.get<double>("ymin"), config.get<double>("ymax"));
298  test->setUseEmptyBins(config.get<bool>("useEmptyBins"));
299  return test;
300  }},
302  [](auto const& config, std::string& name) {
303  auto test = std::make_unique<DeadChannel>(name);
304  test->setWarningProb(config.get<float>("warning", QCriterion::WARNING_PROB_THRESHOLD));
305  test->setErrorProb(config.get<float>("error", QCriterion::ERROR_PROB_THRESHOLD));
306  test->setThreshold(config.get<float>("threshold"));
307  return test;
308  }},
310  [](auto const& config, std::string& name) {
311  auto test = std::make_unique<MeanWithinExpected>(name);
312  test->setWarningProb(config.get<float>("warning", QCriterion::WARNING_PROB_THRESHOLD));
313  test->setErrorProb(config.get<float>("error", QCriterion::ERROR_PROB_THRESHOLD));
314  test->setMinimumEntries(config.get<int>("minEntries", 0));
315  test->setExpectedMean(config.get<double>("mean"));
316  if (config.get<double>("useSigma", 0))
317  test->useSigma(config.get<double>("useSigma"));
318  if (config.get<int>("useRMS", 0))
319  test->useRMS();
320  if (config.get<int>("useRange", 0))
321  test->useRange(config.get<float>("xmin"), config.get<float>("xmax"));
322  return test;
323  }},
324  {NoisyChannel::getAlgoName(), [](auto const& config, std::string& name) {
325  auto test = std::make_unique<NoisyChannel>(name);
326  test->setWarningProb(config.get<float>("warning", QCriterion::WARNING_PROB_THRESHOLD));
327  test->setErrorProb(config.get<float>("error", QCriterion::ERROR_PROB_THRESHOLD));
328  test->setTolerance(config.get<double>("tolerance"));
329  test->setNumNeighbors(config.get<double>("neighbours"));
330  return test;
331  }}};
332 #undef get
333 
334  auto maker = qtestmakers.find(config.get<std::string>("TYPE"));
335  // Check if the type is known, error out otherwise.
336  if (maker == qtestmakers.end())
337  return nullptr;
338 
339  // The QTest XML format has structure
340  // <QTEST><TYPE>QTestClass</TYPE><PARAM name="thing">value</PARAM>...</QTEST>
341  // but that is a pain to read with property_tree. So we reorder the structure
342  // and add a child "thing" with data "value" for each param to a new tree.
343  // Then the qtestmakers can just config.get<type>("thing").
344  boost::property_tree::ptree reordered;
345  for (auto kv : config) {
346  // TODO: case sensitive?
347  if (kv.first == "PARAM") {
348  reordered.put(kv.second.get<std::string>("<xmlattr>.name"), kv.second.data());
349  }
350  }
351 
352  auto name = config.get<std::string>("<xmlattr>.name");
353  return maker->second(reordered, name);
354 }
355 
357  // We read QTests from the config file into this structure, before
358  // transforming into the final [(pattern, *qtest), ...] structure.
359  struct TestItem {
360  std::unique_ptr<QCriterion> qtest;
361  std::vector<std::string> pathpatterns;
362  };
363  std::map<std::string, TestItem> qtestmap;
364 
365  // read XML using property tree. Should throw on error.
366  boost::property_tree::ptree xml;
367  boost::property_tree::read_xml(file, xml);
368 
369  auto it = xml.find("TESTSCONFIGURATION");
370  if (it == xml.not_found()) {
371  throw cms::Exception("QualityTester") << "QTest XML needs to have a TESTSCONFIGURATION node.";
372  }
373  auto& testconfig = it->second;
374  for (auto& kv : testconfig) {
375  // QTEST describes a QTest object (actually QCriterion) with its parameters
376  if (kv.first == "QTEST") {
377  auto& qtestconfig = kv.second;
378  auto name = qtestconfig.get<std::string>("<xmlattr>.name");
379  auto value = makeQCriterion(qtestconfig);
380  // LINK and QTEST can be in any order, so this element may or may not exist
381  qtestmap[name].qtest = std::move(value);
382  } // else
383  // LINK associates one ore more QTest referenced by name to a ME path pattern.
384  if (kv.first == "LINK") {
385  auto& linkconfig = kv.second;
386  auto pathpattern = linkconfig.get<std::string>("<xmlattr>.name");
387  for (auto& subkv : linkconfig) {
388  if (subkv.first == "TestName") {
389  std::string testname = subkv.second.data();
390  bool enabled = subkv.second.get<bool>("<xmlattr>.activate");
391  if (enabled) {
392  // LINK and QTEST can be in any order, so this element may or may not exist
393  qtestmap[testname].pathpatterns.push_back(pathpattern);
394  }
395  }
396  }
397  }
398  // else: unknown tag, but that is fine, its XML
399  }
400 
401  // now that we have all the objects created and references resolved, flatten
402  // the structure into something more useful for evaluating tests.
403  this->qtestobjects.clear();
404  this->qtestpatterns.clear();
405  for (auto& kv : qtestmap) {
406  QCriterion* bareptr = kv.second.qtest.get();
407  for (auto& p : kv.second.pathpatterns) {
408  this->qtestpatterns.push_back(std::make_pair(p, bareptr));
409  }
410  this->qtestobjects.push_back(std::move(kv.second.qtest));
411  }
412  // we could sort the patterns here to allow more performant matching
413  // (using a suffix-array to handle the "*" in the beginning)
414 }
415 
Log< level::Info, true > LogVerbatim
LuminosityBlockNumber_t luminosityBlock() const
std::string getName() const
get name of quality test
Definition: QTest.h:41
std::vector< std::pair< std::string, QCriterion * > > qtestpatterns
std::string fullPath() const
Definition: FileInPath.cc:161
void dqmEndJob(DQMStore::IBooker &, DQMStore::IGetter &) override
float runTest(const MonitorElement *me, QReport &qr, DQMNet::QValue &qv)
Definition: QTest.h:64
bool getQualityTestsFromFile
~QualityTester() override
Destructor.
Definition: config.py:1
void dqmEndLuminosityBlock(DQMStore::IBooker &, DQMStore::IGetter &, edm::LuminosityBlock const &lumiSeg, edm::EventSetup const &c) override
assert(be >=bs)
constexpr bool enabled
Definition: SoACommon.h:71
T getUntrackedParameter(std::string const &, T const &) const
void configureTests(std::string const &file)
static std::string getAlgoName()
Definition: QTest.h:607
static std::string getAlgoName()
Definition: QTest.h:404
virtual std::vector< dqm::harvesting::MonitorElement * > getAllContents(std::string const &path) const
Definition: DQMStore.cc:641
void performTests(DQMStore::IGetter &igetter)
static std::string getAlgoName()
Definition: QTest.h:155
#define DEFINE_FWK_MODULE(type)
Definition: MakerMacros.h:16
static const float ERROR_PROB_THRESHOLD
Definition: QTest.h:62
std::string Label
Definition: value.py:1
static std::string getAlgoName()
Definition: QTest.h:374
LuminosityBlockID id() const
tuple msg
Definition: mps_check.py:286
void dqmEndRun(DQMStore::IBooker &, DQMStore::IGetter &, const edm::Run &r, const edm::EventSetup &c) override
static std::string getAlgoName()
Definition: QTest.h:204
std::string reportThreshold
static std::string getAlgoName()
get algorithm name
Definition: QTest.h:709
QualityTester(const edm::ParameterSet &ps)
Constructor.
dictionary config
Read in AllInOne config in JSON format.
Definition: DiMuonV_cfg.py:29
std::vector< std::unique_ptr< QCriterion > > qtestobjects
std::pair< typename Association::data_type::first_type, double > match(Reference key, Association association, bool bestMatchByMaxValue)
Generic matching function.
Definition: Utils.h:10
std::unique_ptr< QCriterion > makeQCriterion(boost::property_tree::ptree const &config)
static std::string getAlgoName()
Definition: QTest.h:277
void dqmAnalyze(DQMStore::IBooker &, DQMStore::IGetter &, const edm::Event &e, const edm::EventSetup &c) override
perform the actual quality tests
def move(src, dest)
Definition: eostools.py:511
Definition: Run.h:45
static std::string getAlgoName()
Definition: QTest.h:227
static const float WARNING_PROB_THRESHOLD
default "probability" values for setting warnings & errors when running tests
Definition: QTest.h:61
static std::string getAlgoName()
Definition: QTest.h:179
static std::string getAlgoName()
Definition: QTest.h:653