ATLAS Offline Software
Loading...
Searching...
No Matches
trfValidation.py
Go to the documentation of this file.
1# Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
2
14import fnmatch
15import os
16import re
17import logging
18msg = logging.getLogger(__name__)
19
20from PyJobTransforms.trfExeStepTools import getExecutorStepEventCounts
21from PyJobTransforms.trfExitCodes import trfExit
22from PyJobTransforms.trfLogger import stdLogLevels
23from PyJobTransforms.trfArgClasses import argFile
24
25import PyJobTransforms.trfExceptions as trfExceptions
26import PyJobTransforms.trfUtils as trfUtils
27
28
29
30class ignorePatterns(object):
31
32
36 def __init__(self, files=['atlas_error_mask.db'], extraSearch = []):
37 # Setup structured search patterns
39 self._initalisePatterns(files)
40
41 # Setup extra search patterns
43 self._initialiseSerches(extraSearch)
44
45 @property
47 return self._structuredPatterns
48
49 @property
50 def searchPatterns(self):
51 return self._searchPatterns
52
53 def _initalisePatterns(self, files):
54 for patternFile in files:
55 if patternFile == "None":
56 continue
57 fullName = trfUtils.findFile(os.environ['DATAPATH'], patternFile)
58 if not fullName:
59 msg.warning('Error pattern file {0} could not be found in DATAPATH'.format(patternFile))
60 continue
61 try:
62 with open(fullName) as patternFileHandle:
63 msg.debug('Opened error file {0} from here: {1}'.format(patternFile, fullName))
64
65 for line in patternFileHandle:
66 line = line.strip()
67 if line.startswith('#') or line == '':
68 continue
69 try:
70 # N.B. At the moment release matching is not supported!
71 (who, level, message) = [ s.strip() for s in line.split(',', 2) ]
72 if who == "":
73 # Blank means match anything, so make it so...
74 who = "."
75 reWho = re.compile(who)
76 reMessage = re.compile(message)
77 except ValueError:
78 msg.warning('Could not parse this line as a valid error pattern: {0}'.format(line))
79 continue
80 except re.error as e:
81 msg.warning('Could not parse valid regexp from {0}: {1}'.format(message, e))
82 continue
83
84 msg.debug('Successfully parsed: who={0}, level={1}, message={2}'.format(who, level, message))
85
86 self._structuredPatterns.append({'service': reWho, 'level': level, 'message': reMessage})
87
88 except OSError as xxx_todo_changeme:
89 (errno, errMsg) = xxx_todo_changeme.args
90 msg.warning('Failed to open error pattern file {0}: {1} ({2})'.format(fullName, errMsg, errno))
91
92
93 def _initialiseSerches(self, searchStrings=[]):
94 for string in searchStrings:
95 try:
96 self._searchPatterns.append(re.compile(string))
97 msg.debug('Successfully parsed additional logfile search string: {0}'.format(string))
98 except re.error as e:
99 msg.warning('Could not parse valid regexp from {0}: {1}'.format(string, e))
100
101
102
103
106class logFileReport(object):
107 def __init__(self, logfile=None, msgLimit=10, msgDetailLevel=stdLogLevels['ERROR']):
108
109 # We can have one logfile or a set
110 if isinstance(logfile, str):
111 self._logfile = [logfile, ]
112 else:
113 self._logfile = logfile
114
115 self._msgLimit = msgLimit
116 self._msgDetails = msgDetailLevel
117 self._re = None
118
119 if logfile:
120 self.scanLogFile(logfile)
121
122 def resetReport(self):
123 pass
124
125 def scanLogFile(self):
126 pass
127
128 def worstError(self):
129 pass
130
131 def firstError(self):
132 pass
133
134 def __str__(self):
135 return ''
136
137
138
142
146 def __init__(self, logfile, substepName=None, msgLimit=10, msgDetailLevel=stdLogLevels['ERROR'], ignoreList=None):
147 if ignoreList:
148 self._ignoreList = ignoreList
149 else:
151
152
157 self._regExp = re.compile(r'(?P<service>[^\s]+\w)(.*)\s+(?P<level>' + '|'.join(stdLogLevels) + r')\s+(?P<message>.*)')
158
159 self._metaPat = re.compile(r"MetaData:\s+(.*?)\s*=\s*(.*)$")
160 self._metaData = {}
162 self._substepName = substepName
163 self._msgLimit = msgLimit
164
165 self.resetReport()
166
167 super(athenaLogFileReport, self).__init__(logfile, msgLimit, msgDetailLevel)
168
169
171 @property
172 def python(self):
173 errorDict = {'countSummary': {}, 'details': {}}
174 for level, count in self._levelCounter.items():
175 errorDict['countSummary'][level] = count
176 if self._levelCounter[level] > 0 and len(self._errorDetails[level]) > 0:
177 errorDict['details'][level] = []
178 for error in self._errorDetails[level]:
179 errorDict['details'][level].append(error)
180 return errorDict
181
182 def resetReport(self):
184 for level in list(stdLogLevels) + ['UNKNOWN', 'IGNORED']:
185 self._levelCounter[level] = 0
186
188 self._eventLoopWarnings = []
189 for level in self._levelCounter:
190 self._errorDetails[level] = []
191 # Format:
192 # List of dicts {'message': errMsg, 'firstLine': lineNo, 'count': N}
193 self._dbbytes = 0
194 self._dbtime = 0.0
195
196
198 def knowledgeFileHandler(self, knowledgefile):
199 # load abnormal/error line(s) from the knowledge file(s)
200 linesList = []
201 fullName = trfUtils.findFile(os.environ['DATAPATH'], knowledgefile)
202 if not fullName:
203 msg.warning('Knowledge file {0} could not be found in DATAPATH'.format(knowledgefile))
204 else:
205 try:
206 with open(fullName) as knowledgeFileHandle:
207 msg.debug('Opened knowledge file {0} from here: {1}'.format(knowledgefile, fullName))
208
209 for line in knowledgeFileHandle:
210 if line.startswith('#') or line == '' or line =='\n':
211 continue
212 line = line.rstrip('\n')
213 linesList.append(line)
214 except OSError as e:
215 msg.warning('Failed to open knowledge file {0}: {1}'.format(fullName, e))
216 return linesList
217
218 def scanLogFile(self, resetReport=False):
219
220 nonStandardErrorsList = self.knowledgeFileHandler('nonStandardErrors.db')
221
222 if resetReport:
223 self.resetReport()
224
225 for log in self._logfile:
226 msg.debug('Now scanning logfile {0}'.format(log))
227 seenNonStandardError = ''
228 customLogParser = None
229 if log == 'log.generate':
230 from EvgenProdTools.EvgenParserTool import evgenParserTool
231 customLogParser = evgenParserTool()
232 # N.B. Use the generator so that lines can be grabbed by subroutines, e.g., core dump svc reporter
233 try:
234 myGen = trfUtils.lineByLine(log, substepName=self._substepName)
235 except IOError as e:
236 msg.error('Failed to open transform logfile {0}: {1:s}'.format(log, e))
237 # Return this as a small report
238 self._levelCounter['ERROR'] = 1
239 self._errorDetails['ERROR'] = {'message': str(e), 'firstLine': 0, 'count': 1}
240 return
241 # Detect whether we are in the event loop part of the log file
242 inEventLoop = False
243 for line, lineCounter in myGen:
244 if '===>>> start processing event' in line: inEventLoop = True
245 if 'Application Manager Stopped successfully' in line: inEventLoop = False
246
247 # In case we have enabled a custom log parser, run the line through it first
248 if customLogParser is not None:
249 customLogParser.processLine(line)
250 # Search for metadata strings
251 m = self._metaPat.search(line)
252 if m is not None:
253 key, value = m.groups()
254 self._metaData[key] = value
255
256 m = self._regExp.match(line)
257 if m is None:
258 # We didn't manage to get a recognised standard line from the file
259 # But we can check for certain other interesting things, like core dumps
260 if 'Core dump from CoreDumpSvc' in line:
261 msg.warning('Detected CoreDumpSvc report - activating core dump svc grabber')
262 self.coreDumpSvcParser(log, myGen, line, lineCounter)
263 continue
264 # Add the G4 exceptipon parsers
265 if 'G4Exception-START' in line:
266 msg.warning('Detected G4 exception report - activating G4 exception grabber')
267 self.g4ExceptionParser(myGen, line, lineCounter, 40)
268 continue
269 if '*** G4Exception' in line:
270 msg.warning('Detected G4 9.4 exception report - activating G4 exception grabber')
271 self.g494ExceptionParser(myGen, line, lineCounter)
272 continue
273 # Add the python exception parser
274 if 'Shortened traceback (most recent user call last)' in line:
275 msg.warning('Detected python exception - activating python exception grabber')
276 self.pythonExceptionParser(log, myGen, line, lineCounter)
277 continue
278 # Add parser for missed bad_alloc
279 if 'terminate called after throwing an instance of \'std::bad_alloc\'' in line:
280 msg.warning('Detected bad_alloc!')
281 self.badAllocExceptionParser(myGen, line, lineCounter)
282 continue
283 # Parser for ROOT reporting a stale file handle (see ATLASG-448)
284 # Amendment: Generalize the search (see ATLASRECTS-7121)
285 if 'Error in <TFile::ReadBuffer>' in line:
286 self.rootSysErrorParser(myGen, line, lineCounter)
287 continue
288
289 if 'Error in <TFile::WriteBuffer>' in line:
290 self.rootSysErrorParser(myGen, line, lineCounter)
291 continue
292 # Check if the line is among the non-standard logging errors from the knowledge file
293 if any(line in l for l in nonStandardErrorsList):
294 seenNonStandardError = line
295 continue
296
297 msg.debug('Non-standard line in %s: %s', log, line)
298 self._levelCounter['UNKNOWN'] += 1
299 continue
300
301 # Line was matched successfully
302 fields = {}
303 for matchKey in ('service', 'level', 'message'):
304 fields[matchKey] = m.group(matchKey)
305 msg.debug('Line parsed as: {0}'.format(fields))
306
307 # If this is a WARNING and we passed the start of the event loop,
308 # add it to special list
309 if (fields['level'] == 'WARNING') and inEventLoop:
310 self._eventLoopWarnings.append(fields)
311
312 # Check this is not in our ignore list
313 ignoreFlag = False
314 for ignorePat in self._ignoreList.structuredPatterns:
315 serviceMatch = ignorePat['service'].match(fields['service'])
316 levelMatch = (ignorePat['level'] == "" or ignorePat['level'] == fields['level'])
317 messageMatch = ignorePat['message'].match(fields['message'])
318 if serviceMatch and levelMatch and messageMatch:
319 msg.info('Error message "{0}" was ignored at line {1} (structured match)'.format(line, lineCounter))
320 ignoreFlag = True
321 break
322 if ignoreFlag is False:
323 for searchPat in self._ignoreList.searchPatterns:
324 if searchPat.search(line):
325 msg.info('Error message "{0}" was ignored at line {1} (search match)'.format(line, lineCounter))
326 ignoreFlag = True
327 break
328 if ignoreFlag:
329 # Got an ignore - message this to a special IGNORED error
330 fields['level'] = 'IGNORED'
331 else:
332 # Some special handling for specific errors (maybe generalise this if
333 # there end up being too many special cases)
334 # Upgrade bad_alloc to CATASTROPHE to allow for better automated handling of
335 # jobs that run out of memory
336 if 'std::bad_alloc' in fields['message']:
337 fields['level'] = 'CATASTROPHE'
338
339 # concatenate the seen non-standard logging error to the FATAL
340 if fields['level'] == 'FATAL':
341 if seenNonStandardError:
342 line += '; ' + seenNonStandardError
343
344 # Count this error
345 self._levelCounter[fields['level']] += 1
346
347 # Record some error details
348 # N.B. We record 'IGNORED' errors as these really should be flagged for fixing
349 if fields['level'] == 'IGNORED' or stdLogLevels[fields['level']] >= self._msgDetails:
350 if self._levelCounter[fields['level']] <= self._msgLimit:
351 detailsHandled = False
352 for seenError in self._errorDetails[fields['level']]:
353 if seenError['message'] == line:
354 seenError['count'] += 1
355 detailsHandled = True
356 break
357 if detailsHandled is False:
358 self._errorDetails[fields['level']].append({'message': line, 'firstLine': lineCounter, 'count': 1})
359 elif self._levelCounter[fields['level']] == self._msgLimit + 1:
360 msg.warning("Found message number {0} at level {1} - this and further messages will be supressed from the report".format(self._levelCounter[fields['level']], fields['level']))
361 else:
362 # Overcounted
363 pass
364 if 'Total payload read from IOVDb' in fields['message']:
365 msg.debug("Found COOL payload information at line {0}".format(line))
366 a = re.match(r'(\D+)(?P<bytes>\d+)(\D+)(?P<time>\d+[.]?\d*)(\D+)', fields['message'])
367 self._dbbytes += int(a.group('bytes'))
368 self._dbtime += float(a.group('time'))
369 # Finally, if we have a custom log parser, use it to update the metadata dictionary
370 if customLogParser is not None:
371 customLogParser.report()
372 self._metaData = customLogParser.updateMetadata( self._metaData )
373
374
375 def dbMonitor(self):
376 return {'bytes' : self._dbbytes, 'time' : self._dbtime} if self._dbbytes > 0 or self._dbtime > 0 else None
377
378
379 def worstError(self):
380 worst = stdLogLevels['DEBUG']
381 worstName = 'DEBUG'
382 for lvl, count in self._levelCounter.items():
383 if count > 0 and stdLogLevels.get(lvl, 0) > worst:
384 worstName = lvl
385 worst = stdLogLevels[lvl]
386 if len(self._errorDetails[worstName]) > 0:
387 firstError = self._errorDetails[worstName][0]
388 else:
389 firstError = None
390
391 return {'level': worstName, 'nLevel': worst, 'firstError': firstError}
392
393
394 def firstError(self, floor='ERROR'):
395 firstLine = firstError = None
396 firstLevel = stdLogLevels[floor]
397 firstName = floor
398 for lvl, count in self._levelCounter.items():
399 if (count > 0 and stdLogLevels.get(lvl, 0) >= stdLogLevels[floor] and
400 (firstError is None or self._errorDetails[lvl][0]['firstLine'] < firstLine)):
401 firstLine = self._errorDetails[lvl][0]['firstLine']
402 firstLevel = stdLogLevels[lvl]
403 firstName = lvl
404 firstError = self._errorDetails[lvl][0]
405
406 return {'level': firstName, 'nLevel': firstLevel, 'firstError': firstError}
407
409 eventLoopWarnings = []
410 for item in self._eventLoopWarnings:
411 if item in [element['item'] for element in eventLoopWarnings]:
412 continue
413 count = self._eventLoopWarnings.count(item)
414 eventLoopWarnings.append({'item':item, 'count': count})
415 return eventLoopWarnings
416
417 def moreDetails(self, log, firstline, firstLineCount, knowledgeFile, offset=0):
418 # Look for "abnormal" and "last normal" line(s)
419 # Make a list of last e.g. 50 lines before core dump
420 abnormalLinesList = self.knowledgeFileHandler(knowledgeFile)
421 linesToBeScanned = 50
422 seenAbnormalLines = []
423 abnormalLinesReport = {}
424 lastNormalLineReport = {}
425
426 linesList = []
427 myGen = trfUtils.lineByLine(log)
428 for line, linecounter in myGen:
429 if linecounter in range(firstLineCount - linesToBeScanned, firstLineCount-offset):
430 linesList.append([linecounter, line])
431 elif linecounter == firstLineCount:
432 break
433
434 for linecounter, line in reversed(linesList):
435 if re.findall(r'|'.join(abnormalLinesList), line):
436 seenLine = False
437 for dic in seenAbnormalLines:
438 # count repetitions or similar (e.g. first 15 char) abnormal lines
439 if dic['message'] == line or dic['message'][0:15] == line[0:15]:
440 dic['count'] += 1
441 seenLine = True
442 break
443 if seenLine is False:
444 seenAbnormalLines.append({'message': line, 'firstLine': linecounter, 'count': 1})
445 else:
446 if line != '':
447 lastNormalLineReport = {'message': line, 'firstLine': linecounter, 'count': 1}
448 break
449 else:
450 continue
451
452 # Write the list of abnormal lines into the abnormalLinesReport dictionary
453 # The keys of each abnormal line have a number suffix starting with 0
454 # e.g., first abnormal line's keys are :{'mesage0', 'firstLine0', 'count0'}
455
456 for a in range(len(seenAbnormalLines)):
457 abnormalLinesReport.update({'message{0}'.format(a): seenAbnormalLines[a]['message'], 'firstLine{0}'.format(a): seenAbnormalLines[a]['firstLine'],
458 'count{0}'.format(a): seenAbnormalLines[a]['count']})
459
460 return {'abnormalLines': abnormalLinesReport, 'lastNormalLine': lastNormalLineReport}
461
462
463
469 def coreDumpSvcParser(self, log, lineGenerator, firstline, firstLineCount):
470 _eventCounter = _run = _event = _currentAlgorithm = _functionLine = _currentFunction = None
471 coreDumpReport = 'Core dump from CoreDumpSvc'
472 # Number of lines to ignore above 'core dump' when looking for abnormal lines
473 offset = 1
474 coreDumpDetailsReport = {}
475
476 for line, linecounter in lineGenerator:
477 m = self._regExp.match(line)
478 if m is None:
479 if 'Caught signal 11(Segmentation fault)' in line:
480 coreDumpReport = 'Segmentation fault'
481 if 'Event counter' in line:
482 _eventCounter = line
483
484 #Lookup: 'EventID: [Run,Evt,Lumi,Time,BunchCross,DetMask] = [267599,7146597,1,1434123751:0,0,0x0,0x0,0x0]'
485 if 'EventID' in line:
486 match = re.findall(r'\‍[.*?\‍]', line)
487 if match and match.__len__() >= 2: # Assuming the line contains at-least one key-value pair.
488 brackets = "[]"
489 commaDelimer = ','
490 keys = (match[0].strip(brackets)).split(commaDelimer)
491 values = (match[1].strip(brackets)).split(commaDelimer)
492
493 if 'Run' in keys:
494 _run = 'Run: ' + values[keys.index('Run')]
495
496 if 'Evt' in keys:
497 _event = 'Evt: ' + values[keys.index('Evt')]
498
499 if 'Current algorithm' in line:
500 _currentAlgorithm = line
501 if '<signal handler called>' in line:
502 _functionLine = linecounter+1
503 if _functionLine and linecounter is _functionLine:
504 if ' in ' in line:
505 _currentFunction = 'Current Function: ' + line.split(' in ')[1].split()[0]
506 else:
507 _currentFunction = 'Current Function: ' + line.split()[1]
508 else:
509 # Can this be done - we want to push the line back into the generator to be
510 # reparsed in the normal way (might need to make the generator a class with the
511 # __exec__ method supported (to get the line), so that we can then add a
512 # pushback onto an internal FIFO stack
513 # lineGenerator.pushback(line)
514 break
515 _eventCounter = 'Event counter: unknown' if not _eventCounter else _eventCounter
516 _run = 'Run: unknown' if not _run else _run
517 _event = 'Evt: unknown' if not _event else _event
518 _currentAlgorithm = 'Current algorithm: unknown' if not _currentAlgorithm else _currentAlgorithm
519 _currentFunction = 'Current Function: unknown' if not _currentFunction else _currentFunction
520 coreDumpReport = '{0}: {1}; {2}; {3}; {4}; {5}'.format(coreDumpReport, _eventCounter, _run, _event, _currentAlgorithm, _currentFunction)
521
522 coreDumpDetailsReport = self.moreDetails(log, firstline, firstLineCount, 'knowledgeFile.db', offset)
523 abnormalLines = coreDumpDetailsReport['abnormalLines']
524
525 # concatenate an extract of first seen abnormal line to the core dump message
526 if 'message0' in abnormalLines.keys():
527 coreDumpReport += '; Abnormal line seen just before core dump: ' + abnormalLines['message0'][0:30] + '...[truncated] ' + '(see the jobReport)'
528
529 # Core dumps are always fatal...
530 msg.debug('Identified core dump - adding to error detail report')
531 self._levelCounter['FATAL'] += 1
532 self._errorDetails['FATAL'].append({'moreDetails': coreDumpDetailsReport, 'message': coreDumpReport, 'firstLine': firstLineCount, 'count': 1})
533
534
535 def g494ExceptionParser(self, lineGenerator, firstline, firstLineCount):
536 g4Report = firstline
537 g4lines = 1
538 if 'Aborting execution' not in g4Report:
539 for line, linecounter in lineGenerator:
540 g4Report += os.linesep + line
541 g4lines += 1
542 # Test for the closing string
543 if '*** ' in line:
544 break
545 if g4lines >= 25:
546 msg.warning('G4 exception closing string not found within {0} log lines of line {1}'.format(g4lines, firstLineCount))
547 break
548
549 # G4 exceptions can be fatal or they can be warnings...
550 msg.debug('Identified G4 exception - adding to error detail report')
551 if "just a warning" in g4Report:
552 if self._levelCounter['WARNING'] <= self._msgLimit:
553 self._levelCounter['WARNING'] += 1
554 self._errorDetails['WARNING'].append({'message': g4Report, 'firstLine': firstLineCount, 'count': 1})
555 elif self._levelCounter['WARNING'] == self._msgLimit + 1:
556 msg.warning("Found message number {0} at level WARNING - this and further messages will be supressed from the report".format(self._levelCounter['WARNING']))
557 else:
558 self._levelCounter['FATAL'] += 1
559 self._errorDetails['FATAL'].append({'message': g4Report, 'firstLine': firstLineCount, 'count': 1})
560
561 def g4ExceptionParser(self, lineGenerator, firstline, firstLineCount, g4ExceptionLineDepth):
562 g4Report = firstline
563 g4lines = 1
564 for line, linecounter in lineGenerator:
565 g4Report += os.linesep + line
566 g4lines += 1
567 # Test for the closing string
568 if 'G4Exception-END' in line:
569 break
570 if g4lines >= g4ExceptionLineDepth:
571 msg.warning('G4 exception closing string not found within {0} log lines of line {1}'.format(g4lines, firstLineCount))
572 break
573
574 # G4 exceptions can be fatal or they can be warnings...
575 msg.debug('Identified G4 exception - adding to error detail report')
576 if "-------- WWWW -------" in g4Report:
577 if self._levelCounter['WARNING'] <= self._msgLimit:
578 self._levelCounter['WARNING'] += 1
579 self._errorDetails['WARNING'].append({'message': g4Report, 'firstLine': firstLineCount, 'count': 1})
580 elif self._levelCounter['WARNING'] == self._msgLimit + 1:
581 msg.warning("Found message number {0} at level WARNING - this and further messages will be supressed from the report".format(self._levelCounter['WARNING']))
582 else:
583 self._levelCounter['FATAL'] += 1
584 self._errorDetails['FATAL'].append({'message': g4Report, 'firstLine': firstLineCount, 'count': 1})
585
586
587 def pythonExceptionParser(self, log, lineGenerator, firstline, firstLineCount):
588 pythonExceptionReport = ""
589 lastLine = firstline
590 lastLine2 = firstline
591 pythonErrorLine = firstLineCount
592 pyLines = 1
593 for line, linecounter in lineGenerator:
594 if 'Py:Athena' in line and 'INFO leaving with code' in line:
595 if len(lastLine)> 0:
596 pythonExceptionReport = lastLine
597 pythonErrorLine = linecounter-1
598 else: # Sometimes there is a blank line after the exception
599 pythonExceptionReport = lastLine2
600 pythonErrorLine = linecounter-2
601 break
602 if pyLines >= 25:
603 msg.warning('Could not identify python exception correctly scanning {0} log lines after line {1}'.format(pyLines, firstLineCount))
604 pythonExceptionReport = "Unable to identify specific exception"
605 pythonErrorLine = firstLineCount
606 break
607 lastLine2 = lastLine
608 lastLine = line
609 pyLines += 1
610
611 pythonExceptionDetailsReport = self.moreDetails(log, firstline, firstLineCount, 'knowledgeFile.db')
612 abnormalLines = pythonExceptionDetailsReport['abnormalLines']
613
614 # concatenate an extract of first seen abnormal line to pythonExceptionReport
615 if 'message0' in abnormalLines.keys():
616 pythonExceptionReport += '; Abnormal line seen just before python exception: ' + abnormalLines['message0'][0:30] + '...[truncated] ' + '(see the jobReport)'
617
618 msg.debug('Identified python exception - adding to error detail report')
619 self._levelCounter['FATAL'] += 1
620 self._errorDetails['FATAL'].append({'moreDetails': pythonExceptionDetailsReport, 'message': pythonExceptionReport, 'firstLine': pythonErrorLine, 'count': 1})
621
622
623 def badAllocExceptionParser(self, lineGenerator, firstline, firstLineCount):
624 badAllocExceptionReport = 'terminate after \'std::bad_alloc\'.'
625
626 msg.debug('Identified bad_alloc - adding to error detail report')
627 self._levelCounter['CATASTROPHE'] += 1
628 self._errorDetails['CATASTROPHE'].append({'message': badAllocExceptionReport, 'firstLine': firstLineCount, 'count': 1})
629
630 def rootSysErrorParser(self, lineGenerator, firstline, firstLineCount):
631 msg.debug('Identified ROOT IO problem - adding to error detail report')
632 self._levelCounter['FATAL'] += 1
633 self._errorDetails['FATAL'].append({'message': firstline, 'firstLine': firstLineCount, 'count': 1})
634
635 def __str__(self):
636 return str(self._levelCounter) + str(self._errorDetails)
637
638
640 def __init__(self, logfile=None, msgLimit=200, msgDetailLevel=stdLogLevels['ERROR']):
643 self.resetReport()
644 super(scriptLogFileReport, self).__init__(logfile, msgLimit, msgDetailLevel)
645
646 def resetReport(self):
647 self._levelCounter.clear()
648 for level in list(stdLogLevels) + ['UNKNOWN', 'IGNORED']:
649 self._levelCounter[level] = 0
650
651 self._errorDetails.clear()
652 for level in self._levelCounter: # List of dicts {'message': errMsg, 'firstLine': lineNo, 'count': N}
653 self._errorDetails[level] = []
654
655 def scanLogFile(self, resetReport=False):
656 if resetReport:
657 self.resetReport()
658
659 for log in self._logfile:
660 msg.info('Scanning logfile {0}'.format(log))
661 try:
662 myGen = trfUtils.lineByLine(log)
663 except IOError as e:
664 msg.error('Failed to open transform logfile {0}: {1:s}'.format(log, e))
665 # Return this as a small report
666 self._levelCounter['ERROR'] = 1
667 self._errorDetails['ERROR'] = {'message': str(e), 'firstLine': 0, 'count': 1}
668 return
669
670 for line, lineCounter in myGen:
671 # TODO: This implementation currently only scans for Root SysErrors.
672 # General solution would be a have common error parser for all system level
673 # errors those all also handled by AthenaLogFileReport.
674 if line.__contains__('Error in <TFile::ReadBuffer>') or \
675 line.__contains__('Error in <TFile::WriteBuffer>'):
676 self.rootSysErrorParser(line, lineCounter)
677
678 # Return the worst error found in the logfile (first error of the most serious type)
679 def worstError(self):
680 worstlevelName = 'DEBUG'
681 worstLevel = stdLogLevels[worstlevelName]
682 for levelName, count in self._levelCounter.items():
683 if count > 0 and stdLogLevels.get(levelName, 0) > worstLevel:
684 worstlevelName = levelName
685 worstLevel = stdLogLevels[levelName]
686
687 if len(self._errorDetails[worstlevelName]) > 0:
688 firstError = self._errorDetails[worstlevelName][0]
689 else:
690 firstError = None
691
692 return {'level': worstlevelName, 'nLevel': worstLevel, 'firstError': firstError}
693
694 def __str__(self):
695 return str(self._levelCounter) + str(self._errorDetails)
696
697 def rootSysErrorParser(self, line, lineCounter):
698 msg.debug('Identified ROOT IO problem - adding to error detail report')
699 self._levelCounter['FATAL'] += 1
700 self._errorDetails['FATAL'].append({'message': line, 'firstLine': lineCounter, 'count': 1})
701
702
705def returnIntegrityOfFile(file, functionName, **kwargs):
706 try:
707 import PyJobTransforms.trfFileValidationFunctions as trfFileValidationFunctions
708 except Exception as exception:
709 msg.error('Failed to import module PyJobTransforms.trfFileValidationFunctions with error {error}'.format(error = exception))
710 raise
711
712 import multiprocessing
713
714 level = kwargs.get('level')
715 if level is not None:
716 if level < msg.getEffectiveLevel():
717 msg.setLevel(level)
718 msg.debug(f"Set logging level of {msg.name!r} to {logging.getLevelName(level)!r}")
719
720 msg.debug(f"Current process: {multiprocessing.current_process().name}")
721
722 validationFunction = getattr(trfFileValidationFunctions, functionName)
723 args = ", ".join(f"{k}={v}" for k, v in kwargs.items())
724 msg.debug(f"Calling {validationFunction.__name__}({file}, {args}) ")
725 return validationFunction(file, **kwargs)
726
727
728
731def performStandardFileValidation(dictionary, io, parallelMode = False, multithreadedMode=False):
732 if io == "output":
733 if multithreadedMode:
734 os.environ['TRF_MULTITHREADED_VALIDATION'] = 'TRUE'
735 if parallelMode is False:
736 msg.info('Starting legacy (serial) file validation')
737 for (key, arg) in dictionary.items():
738 if not isinstance(arg, argFile):
739 continue
740 if not arg.io == io:
741 continue
742 if arg.auxiliaryFile:
743 continue
744
745 msg.info('Validating data type %s...', key)
746
747 for fname in arg.value:
748 msg.info('Validating file %s...', fname)
749
750 if io == "output":
751 msg.info('{0}: Testing corruption...'.format(fname))
752 if arg.getSingleMetadata(fname, 'integrity') is True:
753 msg.info('Corruption test passed.')
754 elif arg.getSingleMetadata(fname, 'integrity') is False:
755 msg.error('Corruption test failed.')
756 raise trfExceptions.TransformValidationException(trfExit.nameToCode('TRF_EXEC_VALIDATION_FAIL'), 'File %s did not pass corruption test' % fname)
757 elif arg.getSingleMetadata(fname, 'integrity') == 'UNDEFINED':
758 msg.info('No corruption test defined.')
759 elif arg.getSingleMetadata(fname, 'integrity') is None:
760 msg.error('Could not check for file integrity')
761 raise trfExceptions.TransformValidationException(trfExit.nameToCode('TRF_EXEC_VALIDATION_FAIL'), 'File %s might be missing' % fname)
762 else:
763 msg.error('Unknown rc from corruption test.')
764 raise trfExceptions.TransformValidationException(trfExit.nameToCode('TRF_EXEC_VALIDATION_FAIL'), 'File %s did not pass corruption test' % fname)
765
766
767 msg.info('{0}: Testing event count...'.format(fname))
768 if arg.getSingleMetadata(fname, 'nentries') is not None:
769 msg.info('Event counting test passed ({0!s} events).'.format(arg.getSingleMetadata(fname, 'nentries')))
770 else:
771 msg.error('Event counting test failed.')
772 raise trfExceptions.TransformValidationException(trfExit.nameToCode('TRF_EXEC_VALIDATION_FAIL'), 'File %s did not pass corruption test' % fname)
773
774
775 msg.info('{0}: Checking if guid exists...'.format(fname))
776 if arg.getSingleMetadata(fname, 'file_guid') is None:
777 msg.error('Guid could not be determined.')
778 raise trfExceptions.TransformValidationException(trfExit.nameToCode('TRF_EXEC_VALIDATION_FAIL'), 'File %s did not pass corruption test' % fname)
779 elif arg.getSingleMetadata(fname, 'file_guid') == 'UNDEFINED':
780 msg.info('Guid not defined.')
781 else:
782 msg.info('Guid is %s', arg.getSingleMetadata(fname, 'file_guid'))
783 msg.info('Stopping legacy (serial) file validation')
784 elif parallelMode is True:
785 msg.info('Starting parallel file validation')
786 # Create lists of files and args. These lists are to be used with zip in
787 # order to check and update file integrity metadata as appropriate.
788 fileList = []
789 argList = []
790 # Create a list of the integrity functions for files.
791 integrityFunctionList = []
792 # Create a list for collation of file validation jobs for submission to
793 # the parallel job processor.
794 jobs = []
795 msg.debug('Collating list of files for validation')
796 for (key, arg) in dictionary.items():
797 if not isinstance(arg, argFile):
798 continue
799 if not arg.io == io:
800 continue
801 for fname in arg.value:
802 msg.debug('Appending file {fileName} to list of files for validation'.format(fileName = str(fname)))
803 # Append the current file to the file list.
804 fileList.append(fname)
805 # Append the current arg to the arg list.
806 argList.append(arg)
807 # Append the current integrity function name to the integrity
808 # function list if it exists. If it does not exist, raise an
809 # exception.
810 if io == "output":
811 try:
812 integrityFunctionList.append(arg.integrityFunction)
813 except AttributeError as e:
814 errmsg = f'Validation function for file {fname} of type'\
815 f' {type(arg).__name__!r} not available for parallel file validation: {e}'
816 msg.error(errmsg)
818 trfExit.nameToCode('TRF_EXEC_VALIDATION_FAIL'), errmsg)
819 # Compose a job for validation of the current file using the
820 # appropriate validation function, which is derived from the
821 # associated data attribute arg.integrityFunction.
822 jobs.append(
824 name = "validation of file {fileName}".format(
825 fileName = str(fname)),
826 workFunction = returnIntegrityOfFile,
827 workFunctionKeywordArguments = {
828 'file': fname,
829 'functionName': arg.integrityFunction,
830 'level': msg.getEffectiveLevel(),
831 },
832 workFunctionTimeout = 600
833 )
834 )
835 # Contain the file validation jobs in a job group for submission to the
836 # parallel job processor.
837 if io == "output":
838 jobGroup1 = trfUtils.JobGroup(
839 name = "standard file validation",
840 jobs = jobs
841 )
842 # Prepare the parallel job processor.
843 parallelJobProcessor1 = trfUtils.ParallelJobProcessor(numberOfProcesses=len(jobs))
844 # Submit the file validation jobs to the parallel job processor.
845 msg.info('Submitting file validation jobs to parallel job processor')
846 parallelJobProcessor1.submit(jobSubmission = jobGroup1)
847 resultsList = parallelJobProcessor1.getResults()
848 msg.info('Parallel file validation complete')
849 # Update file metadata with integrity results using the lists fileList,
850 # argList and resultsList.
851 msg.info('Processing file integrity results')
852 for currentFile, currentArg, currentIntegrityFunction, currentResult in zip(fileList, argList, integrityFunctionList, resultsList):
853 msg.info('{IO} file {fileName} has integrity status {integrityStatus} as determined by integrity function {integrityFunction}'.format(
854 IO = str(io),
855 fileName = str(currentFile),
856 integrityStatus = str(currentResult),
857 integrityFunction = str(currentIntegrityFunction)
858 ))
859 # If the first (Boolean) element of the result tuple for the current
860 # file is True, update the integrity metadata. If it is False, raise
861 # an exception.
862 if currentResult[0] is True:
863 msg.info('Updating integrity metadata for file {fileName}'.format(fileName = str(currentFile)))
864 currentArg._setMetadata(files=[currentFile,], metadataKeys={'integrity': currentResult[0]})
865 else:
866 exceptionMessage = "{IO} file validation failure on file {fileName} with integrity status {integrityStatus} as determined by integrity function {integrityFunction}".format(
867 IO = str(io),
868 fileName = str(currentFile),
869 integrityStatus = str(currentResult),
870 integrityFunction = str(currentIntegrityFunction)
871 )
872 msg.error("exception message: {exceptionMessage}".format(
873 exceptionMessage = exceptionMessage
874 ))
875 exitCodeName = 'TRF_OUTPUT_FILE_VALIDATION_FAIL'
877 trfExit.nameToCode(exitCodeName),
878 exceptionMessage
879 )
880 # Perform a check to determine if the file integrity metadata is
881 # correct.
882 if currentArg.getSingleMetadata(currentFile, metadataKey = 'integrity', populate = False) == currentResult[0]:
883 msg.debug("file integrity metadata update successful")
884 else:
885 msg.error("file integrity metadata update unsuccessful")
886
887 metadataKeys = ('nentries', 'file_guid')
888 msg.info(f"{', '.join(fileList)}: Checking {', '.join(map(repr, metadataKeys))} ...")
889 metadata = {fname: arg.getMetadata(fname, metadataKeys=metadataKeys)[fname]
890 for fname, arg in zip(fileList, argList, strict=True)}
891 success = {fname: md for fname, md in metadata.items() if None not in md.values()}
892 if len(success):
893 lines = [
894 f"{fname}: {' '.join(f'{k}={v}' for k, v in md.items())}"
895 for fname, md in success.items()
896 ]
897 msg.info("Checked\n\t" + "\n\t".join(lines))
898 if len(success) != len(metadata):
899 missing = ", ".join(fname for fname in metadata if fname not in success)
900 keys = '" and/or "'.join(metadataKeys)
901 errmsg = f'{missing}: Could not determine "{keys}"'
902 msg.error(errmsg)
903 raise trfExceptions.TransformValidationException(trfExit.nameToCode('TRF_EXEC_VALIDATION_FAIL'), errmsg)
904 msg.info('Stopping parallel file validation')
905
906
907
908class eventMatch(object):
909
910
917 def __init__(self, executor, eventCountConf=None, eventCountConfOverwrite=False):
918 self._executor = executor
919 self._eventCount = None
920
921
932 simEventEff = 0.995
934 self._eventCountConf['EVNT'] = {'EVNT_MRG':"match", "HITS": simEventEff, "EVNT_TR": "filter", "DAOD_TRUTH*" : "match"}
935 self._eventCountConf['EVNT_TR'] = {'HITS': simEventEff}
936 self._eventCountConf['HITS'] = {'RDO':"match", 'HITS_RSM': simEventEff, "HITS_MRG":"match", 'HITS_FILT': simEventEff, "RDO_FILT": "filter", "DAOD_TRUTH*" : "match", "HIST_SIM" : "match"}
937 self._eventCountConf['BS'] = {'ESD': "match", 'DRAW_*':"filter", 'NTUP_*':"filter", "BS_MRG":"match", 'DESD*': "filter", 'AOD':"match", 'DAOD*':"filter", "DAOD_PHYS":"match", "DAOD_PHYSLITE":"match"}
938 self._eventCountConf['RDO*'] = {'ESD': "match", 'DRAW_*':"filter", 'NTUP_*':"filter", "RDO_MRG":"match", "RDO_TRIG":"match", 'AOD':"match", 'DAOD*':"filter", "DAOD_PHYS":"match", "DAOD_PHYSLITE":"match", "HIST_DIGI":"match"}
939 self._eventCountConf['ESD'] = {'ESD_MRG': "match", 'AOD':"match", 'DESD*':"filter", 'DAOD_*':"filter", 'NTUP_*':"filter", "DAOD_PHYS":"match", "DAOD_PHYSLITE":"match"}
940 self._eventCountConf['AOD'] = {'AOD_MRG' : "match", 'TAG':"match", "NTUP_*":"filter", "DAOD_*":"filter", "DAOD_PHYS":"match", "DAOD_PHYSLITE":"match"}
941 self._eventCountConf['AOD_MRG'] = {'TAG':"match"}
942 self._eventCountConf['DAOD_*'] = {'DAOD_*_MRG' : "match"}
943 self._eventCountConf['TAG'] = {'TAG_MRG': "match"}
944 self._eventCountConf['HIST'] = {'HIST_MRG': "match"}
945 self._eventCountConf['NTUP_COMMON'] = {'DNTUP*': "filter"}
946 self._eventCountConf['NTUP_*'] = {'NTUP_*_MRG': "match"}
947 # Next one comprises special data type names for smart merging of AthenaMP worker outputs
948 self._eventCountConf['POOL_MRG_INPUT'] = {'POOL_MRG_OUTPUT': "match"}
949
950
951 if eventCountConf:
952 if eventCountConfOverwrite is True:
953 self._eventCountConf = eventCountConf
954 else:
955 self._eventCountConf.update(eventCountConf)
956
957 msg.debug('Event count check configuration is: {0}'.format(self._eventCountConf))
958 if hasattr(self._executor, 'name'):
959 msg.debug('Event count check ready for executor {0}'.format(self._executor.name))
960
961 if self._executor is not None:
962 self.configureCheck(override=False)
963
964 @property
965 def eventCount(self):
966 return self._eventCount
967
968
972 def configureCheck(self, override=False):
973 if override:
974 msg.info('Overriding check configuration with: {0}'.format(override))
975 self._inEventDict = override['inEventDict']
976 self._outEventDict = override['outEventDict']
977 self._skipEvents = override['skipEvents']
978 self._maxEvents = override['maxEvents']
979 self._evAccEff = override['evAccEff']
980 else:
981 # Input data from executor
982 self._inEventDict = {}
983 for dataTypeName in self._executor.input:
984 try:
985 self._inEventDict[dataTypeName] = self._executor.conf.dataDictionary[dataTypeName].nentries
986 msg.debug('Input data type {0} has {1} events'.format(dataTypeName, self._inEventDict[dataTypeName]))
987 except KeyError:
988 msg.warning('Found no dataDictionary entry for input data type {0}'.format(dataTypeName))
989
990 # Output data from executor
991 self._outEventDict = {}
992 for dataTypeName in self._executor.output:
993 try:
994 self._outEventDict[dataTypeName] = self._executor.conf.dataDictionary[dataTypeName].nentries
995 msg.debug('Output data type {0} has {1} events'.format(dataTypeName, self._outEventDict[dataTypeName]))
996 except KeyError:
997 msg.warning('Found no dataDictionary entry for output data type {0}'.format(dataTypeName))
998
999 # Find if we have a skipEvents applied
1000 if "skipEvents" in self._executor.conf.argdict:
1001 self._skipEvents = self._executor.conf.argdict['skipEvents'].returnMyValue(exe=self._executor)
1002 else:
1003 self._skipEvents = None
1004
1005 # Find if we have a maxEvents applied
1006 if "maxEvents" in self._executor.conf.argdict:
1007 self._maxEvents = self._executor.conf.argdict['maxEvents'].returnMyValue(exe=self._executor)
1008 if self._maxEvents == -1:
1009 self._maxEvents = None
1010 else:
1011 self._maxEvents = None
1012
1013 # Executor substeps handling
1014 if self._executor.conf.totalExecutorSteps > 1 and self._executor.conf.executorStep < self._executor.conf.totalExecutorSteps - 1:
1015 executorEventCounts, executorEventSkips = getExecutorStepEventCounts(self._executor)
1016 self._maxEvents = executorEventCounts[self._executor.conf.executorStep]
1017 self._skipEvents = executorEventSkips[self._executor.conf.executorStep]
1018
1019 # Global eventAcceptanceEfficiency set?
1020 if "eventAcceptanceEfficiency" in self._executor.conf.argdict:
1021 self._evAccEff = self._executor.conf.argdict['eventAcceptanceEfficiency'].returnMyValue(exe=self._executor)
1022 if (self._evAccEff is None):
1023 self._evAccEff = 0.99
1024 else:
1025 self._evAccEff = 0.99
1026
1027 msg.debug("Event check conf: {0} {1}, {2}, {3}, {4}".format(self._inEventDict, self._outEventDict, self._skipEvents,
1028 self._maxEvents, self._evAccEff))
1029
1030
1031
1032 def decide(self):
1033 # We have all that we need to proceed: input and output data, skip and max events plus any efficiency factor
1034 # So loop over the input and output data and make our checks
1035 for inData, neventsInData in self._inEventDict.items():
1036 if not isinstance(neventsInData, int):
1037 msg.warning('File size metadata for {inData} was not countable, found {neventsInData}. No event checks possible for this input data.'.format(inData=inData, neventsInData=neventsInData))
1038 continue
1039 if inData in self._eventCountConf:
1040 inDataKey = inData
1041 else:
1042 # OK, try a glob match in this case (YMMV)
1043 matchedInData = False
1044 for inDataKey in self._eventCountConf:
1045 if fnmatch.fnmatch(inData, inDataKey):
1046 msg.info("Matched input data type {inData} to {inDataKey} by globbing".format(inData=inData, inDataKey=inDataKey))
1047 matchedInData = True
1048 break
1049 if not matchedInData:
1050 msg.warning('No defined event count match for {inData} -> {outData}, so no check(s) possible in this case.'.format(inData=inData, outData=list(self._outEventDict)))
1051 continue
1052
1053 # Now calculate the expected number of processed events for this input
1054 expectedEvents = neventsInData
1055 if self._skipEvents is not None and self._skipEvents > 0:
1056 expectedEvents -= self._skipEvents
1057 if expectedEvents < 0:
1058 msg.warning('skipEvents was set higher than the input events in {inData}: {skipEvents} > {neventsInData}. This is not an error, but it is not a normal configuration. Expected events is now 0.'.format(inData=inData, skipEvents=self._skipEvents, neventsInData=neventsInData))
1059 expectedEvents = 0
1060 if self._maxEvents is not None:
1061 if expectedEvents < self._maxEvents:
1062 if self._skipEvents is not None:
1063 msg.warning('maxEvents was set higher than inputEvents-skipEvents for {inData}: {maxEvents} > {neventsInData}-{skipEvents}. This is not an error, but it is not a normal configuration. Expected events remains {expectedEvents}.'.format(inData=inData, maxEvents=self._maxEvents, neventsInData=neventsInData, skipEvents=self._skipEvents, expectedEvents=expectedEvents))
1064 else:
1065 msg.warning('maxEvents was set higher than inputEvents for {inData}: {maxEvents} > {neventsInData}. This is not an error, but it is not a normal configuration. Expected events remains {expectedEvents}.'.format(inData=inData, maxEvents=self._maxEvents, neventsInData=neventsInData, expectedEvents=expectedEvents))
1066 else:
1067 expectedEvents = self._maxEvents
1068 msg.debug('Expected number of processed events for {0} is {1}'.format(inData, expectedEvents))
1069
1070 # Loop over output data - first find event count configuration
1071 for outData, neventsOutData in self._outEventDict.items():
1072 if not isinstance(neventsOutData, int):
1073 msg.warning('File size metadata for {outData} was not countable, found "{neventsOutData}". No event checks possible for this output data.'.format(outData=outData, neventsOutData=neventsOutData))
1074 continue
1075 if outData in self._eventCountConf[inDataKey]:
1076 checkConf = self._eventCountConf[inDataKey][outData]
1077 outDataKey = outData
1078 else:
1079 # Look for glob matches
1080 checkConf = None
1081 for outDataKey, outDataConf in self._eventCountConf[inDataKey].items():
1082 if fnmatch.fnmatch(outData, outDataKey):
1083 msg.info('Matched output data type {outData} to {outDatakey} by globbing'.format(outData=outData, outDatakey=outDataKey))
1084 outDataKey = outData
1085 checkConf = outDataConf
1086 break
1087 if not checkConf:
1088 msg.warning('No defined event count match for {inData} -> {outData}, so no check possible in this case.'.format(inData=inData, outData=outData))
1089 continue
1090 msg.debug('Event count check for {inData} to {outData} is {checkConf}'.format(inData=inData, outData=outData, checkConf=checkConf))
1091
1092 # Do the check for thsi input/output combination
1093 if checkConf == 'match':
1094 # We need an exact match
1095 if neventsOutData == expectedEvents:
1096 msg.info("Event count check for {inData} to {outData} passed: all processed events found ({neventsOutData} output events)".format(inData=inData, outData=outData, neventsOutData=neventsOutData))
1097 else:
1098 raise trfExceptions.TransformValidationException(trfExit.nameToCode('TRF_EXEC_VALIDATION_EVENTCOUNT'),
1099 'Event count check for {inData} to {outData} failed: found {neventsOutData} events, expected {expectedEvents}'.format(inData=inData, outData=outData, neventsOutData=neventsOutData, expectedEvents=expectedEvents))
1100 elif checkConf == 'filter':
1101 if neventsOutData <= expectedEvents and neventsOutData >= 0:
1102 msg.info("Event count check for {inData} to {outData} passed: found ({neventsOutData} output events selected from {expectedEvents} processed events)".format(inData=inData, outData=outData, neventsOutData=neventsOutData, expectedEvents=expectedEvents))
1103 else:
1104 raise trfExceptions.TransformValidationException(trfExit.nameToCode('TRF_EXEC_VALIDATION_EVENTCOUNT'),
1105 'Event count check for {inData} to {outData} failed: found {neventsOutData} events, expected from 0 to {expectedEvents}'.format(inData=inData, outData=outData, neventsOutData=neventsOutData, expectedEvents=expectedEvents))
1106 elif checkConf == 'minEff':
1107 if neventsOutData >= int(expectedEvents * self._evAccEff) and neventsOutData <= expectedEvents:
1108 msg.info("Event count check for {inData} to {outData} passed: found ({neventsOutData} output events selected from {expectedEvents} processed events)".format(inData=inData, outData=outData, neventsOutData=neventsOutData, expectedEvents=expectedEvents))
1109 else:
1110 raise trfExceptions.TransformValidationException(trfExit.nameToCode('TRF_EXEC_VALIDATION_EVENTCOUNT'),
1111 'Event count check for {inData} to {outData} failed: found {neventsOutData} events, expected from {minEvents} to {expectedEvents}'.format(inData=inData, outData=outData, neventsOutData=neventsOutData,
1112 minEvents=int(expectedEvents * self._evAccEff), expectedEvents=expectedEvents))
1113 elif isinstance(checkConf, (float, int)):
1114 checkConf = float(checkConf)
1115 if checkConf < 0.0 or checkConf > 1.0:
1116 raise trfExceptions.TransformValidationException(trfExit.nameToCode('TRF_EXEC_VALIDATION_EVENTCOUNT'),
1117 'Event count check for {inData} to {outData} is misconfigured: the efficiency factor of {eff} is not between 0 and 1.'.format(inData=inData, outData=outData, eff=checkConf))
1118 if neventsOutData >= int(expectedEvents * checkConf) and neventsOutData <= expectedEvents:
1119 msg.info("Event count check for {inData} to {outData} passed: found ({neventsOutData} output events selected from {expectedEvents} processed events)".format(inData=inData, outData=outData, neventsOutData=neventsOutData, expectedEvents=expectedEvents))
1120 else:
1121 raise trfExceptions.TransformValidationException(trfExit.nameToCode('TRF_EXEC_VALIDATION_EVENTCOUNT'),
1122 'Event count check for {inData} to {outData} failed: found {neventsOutData} events, expected from {minEvents} to {expectedEvents}'.format(inData=inData, outData=outData, neventsOutData=neventsOutData,
1123 minEvents=int(expectedEvents * checkConf), expectedEvents=expectedEvents))
1124 else:
1125 raise trfExceptions.TransformValidationException(trfExit.nameToCode('TRF_EXEC_VALIDATION_EVENTCOUNT'),
1126 'Unrecognised event count configuration for {inData} to {outData}: "{conf}" is not known'.format(inData=inData, outData=outData, conf=checkConf))
1127 self._eventCount = expectedEvents
1128 return True
void clear()
Empty the pool.
JobGroup: a set of Job objects and pieces of information relevant to a given set of Job objects.
Definition trfUtils.py:789
Job: a set of pieces of information relevant to a given work function.
Definition trfUtils.py:725
ParallelJobProcessor: a multiple-process processor of Job objects.
Definition trfUtils.py:869
Logfile suitable for scanning logfiles with an athena flavour, i.e., lines of the form "SERVICE LOGL...
firstError(self, floor='ERROR')
Return the first error found in the logfile above a certain loglevel.
rootSysErrorParser(self, lineGenerator, firstline, firstLineCount)
dbMonitor(self)
Return data volume and time spend to retrieve information from the database.
__init__(self, logfile, substepName=None, msgLimit=10, msgDetailLevel=stdLogLevels['ERROR'], ignoreList=None)
Class constructor.
badAllocExceptionParser(self, lineGenerator, firstline, firstLineCount)
knowledgeFileHandler(self, knowledgefile)
Generally, a knowledge file consists of non-standard logging error/abnormal lines which are left out ...
moreDetails(self, log, firstline, firstLineCount, knowledgeFile, offset=0)
g494ExceptionParser(self, lineGenerator, firstline, firstLineCount)
worstError(self)
Return the worst error found in the logfile (first error of the most serious type).
pythonExceptionParser(self, log, lineGenerator, firstline, firstLineCount)
coreDumpSvcParser(self, log, lineGenerator, firstline, firstLineCount)
Attempt to suck a core dump report from the current logfile This function scans logs in two different...
g4ExceptionParser(self, lineGenerator, firstline, firstLineCount, g4ExceptionLineDepth)
Small class used for vailiadating event counts between input and output files.
__init__(self, executor, eventCountConf=None, eventCountConfOverwrite=False)
check in- and output event counts
decide(self)
Perform an event count check.
configureCheck(self, override=False)
Setup the parameters needed to define particular checks.
Class of patterns that can be ignored from athena logfiles.
_initialiseSerches(self, searchStrings=[])
__init__(self, files=['atlas_error_mask.db'], extraSearch=[])
Load error patterns from files.
A class holding report information from scanning a logfile This is pretty much a virtual class,...
__init__(self, logfile=None, msgLimit=10, msgDetailLevel=stdLogLevels['ERROR'])
__init__(self, logfile=None, msgLimit=200, msgDetailLevel=stdLogLevels['ERROR'])
rootSysErrorParser(self, line, lineCounter)
void search(TDirectory *td, const std::string &s, std::string cwd, node *n)
recursive directory search for TH1 and TH2 and TProfiles
Definition hcg.cxx:743
int count(std::string s, const std::string &regx)
count how many occurances of a regx are in a string
Definition hcg.cxx:148
std::vector< std::string > split(const std::string &s, const std::string &t=":")
Definition hcg.cxx:179
bool match(std::string s1, std::string s2)
match the individual directories of two strings
Definition hcg.cxx:359
Transform argument class definitions.
Module for transform exit codes.
Transform file validation functions.
Logging configuration for ATLAS job transforms.
Transform utility functions.
performStandardFileValidation(dictionary, io, parallelMode=False, multithreadedMode=False)
perform standard file validation @ detail This method performs standard file validation in either ser...
returnIntegrityOfFile(file, functionName, **kwargs)
return integrity of file using appropriate validation function @ detail This method returns the integ...