ATLAS Offline Software
Loading...
Searching...
No Matches
trfExe.py
Go to the documentation of this file.
1# Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
2
8
9import json
10import math
11import os
12import os.path as path
13import re
14import signal
15import subprocess
16import sys
17import time
18
19import logging
20from fnmatch import fnmatch
21msg = logging.getLogger(__name__)
22
23from PyJobTransforms.trfJobOptions import JobOptionsTemplate
24from PyJobTransforms.trfUtils import asetupReport, asetupReleaseIsOlderThan, unpackDBRelease, setupDBRelease, \
25 cvmfsDBReleaseCheck, forceToAlphaNum, \
26 ValgrindCommand, VTuneCommand, isInteractiveEnv, calcCpuTime, calcWallTime, analytic, reportEventsPassedSimFilter
27from PyJobTransforms.trfExeStepTools import commonExecutorStepName, executorStepSuffix
28from PyJobTransforms.trfExitCodes import trfExit
29from PyJobTransforms.trfLogger import stdLogLevels
30from PyJobTransforms.trfMPTools import detectAthenaMPProcs, athenaMPOutputHandler
31from PyJobTransforms.trfMTTools import detectAthenaMTThreads
32
33import PyJobTransforms.trfExceptions as trfExceptions
34import PyJobTransforms.trfValidation as trfValidation
35import PyJobTransforms.trfArgClasses as trfArgClasses
36import PyJobTransforms.trfEnv as trfEnv
38
39
40# Depending on the setting of LANG, sys.stdout may end up with ascii or ansi
41# encoding, rather than utf-8. But Athena uses unicode for some log messages
42# (another example of Gell-Mann's totalitarian principle) and that will result
43# in a crash with python 3. In such a case, force the use of a utf-8 encoded
44# output stream instead.
46 enc = s.encoding.lower()
47 if enc.find('ascii') >= 0 or enc.find('ansi') >= 0:
48 return open (s.fileno(), 'w', encoding='utf-8')
49 return s
50
51
52
57class executorConfig(object):
58
59
63 def __init__(self, argdict={}, dataDictionary={}, firstExecutor=False):
64 self._argdict = argdict
65 self._dataDictionary = dataDictionary
66 self._firstExecutor = firstExecutor
67 self._executorStep = -1
69
70 @property
71 def argdict(self):
72 return self._argdict
73
74 @argdict.setter
75 def argdict(self, value):
76 self._argdict = value
77
78 @property
79 def dataDictionary(self):
80 return self._dataDictionary
81
82 @dataDictionary.setter
83 def dataDictionary(self, value):
84 self._dataDictionary = value
85
86 @property
87 def firstExecutor(self):
88 return self._firstExecutor
89
90 @firstExecutor.setter
91 def firstExecutor(self, value):
92 self._firstExecutor = value
93
94 @property
95 def executorStep(self):
96 return self._executorStep
97
98 @executorStep.setter
99 def executorStep(self, value):
100 self._executorStep = value
101
102 @property
104 return self._totalExecutorSteps
105
106 @totalExecutorSteps.setter
107 def totalExecutorSteps(self, value):
108 self._totalExecutorSteps = value
109
110
113 def setFromTransform(self, trf):
114 self._argdict = trf.argdict
115 self._dataDictionary = trf.dataDictionary
116
117
118 def addToArgdict(self, key, value):
119 self._argdict[key] = value
120
121
122 def addToDataDictionary(self, key, value):
123 self._dataDictionary[key] = value
124
125
126
127class transformExecutor(object):
128
129
138 def __init__(self, name = 'Dummy', trf = None, conf = None, inData = set(), outData = set()):
139 # Some information to produce helpful log messages
140
141 self._name = forceToAlphaNum(name)
142 # Data this executor can start from and produce
143 # Note we transform NULL to inNULL and outNULL as a convenience
144 self._inData = set(inData)
145 self._outData = set(outData)
146 if 'NULL' in self._inData:
147 self._inData.remove('NULL')
148 self._inData.add('inNULL')
149 if 'NULL' in self._outData:
150 self._outData.remove('NULL')
151 self._outData.add('outNULL')
152
153 # It's forbidden for an executor to consume and produce the same datatype
154 dataOverlap = self._inData & self._outData
155 if len(dataOverlap) > 0:
156 raise trfExceptions.TransformSetupException(trfExit.nameToCode('TRF_GRAPH_ERROR'),
157 'Executor definition error, executor {0} is not allowed to produce and consume the same datatypes. Duplicated input/output types {1}'.format(self._name, ' '.join(dataOverlap)))
158
159
162 if conf is not None:
163 self.conf = conf
164 else:
165 self.conf = executorConfig()
166 if trf is not None:
167 self.conf.setFromTransform(trf)
168
169 # Execution status
170 self._hasExecuted = False
171 self._rc = -1
172 self._errMsg = None
173
174 # Validation status
175 self._hasValidated = False
176 self._isValidated = False
177
178 # Extra metadata
179 # This dictionary holds extra metadata for this executor which will be
180 # provided in job reports
182
183
186 self._preExeStart = None
187 self._exeStart = self._exeStop = None
188 self._valStart = self._valStop = None
189 self._memStats = {}
191 self._memFullFile = None
192 self._eventCount = None
193 self._athenaMP = 0
194 self._athenaMT = 0
196 self._dbMonitor = None
197 self._resimevents = None
199 self._containerSetup = None
200
201 # Holder for execution information about any merges done by this executor in MP mode
202 self._myMerger = []
203
204
205
206 @property
207 def myMerger(self):
208 return self._myMerger
209
210 @property
211 def name(self):
212 return self._name
213
214 @name.setter
215 def name(self, value):
216 self._name = value
217
218 @property
219 def substep(self):
220 if '_substep' in dir(self):
221 return self._substep
222 return None
223
224 @property
225 def trf(self):
226 if '_trf' in dir(self):
227 return self._trf
228 return None
229
230 @trf.setter
231 def trf(self, value):
232 self._trf = value
233
234 @property
235 def inData(self):
236
237 if '_inData' in dir(self):
238 return self._inData
239 return None
240
241 @inData.setter
242 def inData(self, value):
243 self._inData = set(value)
244
245 def inDataUpdate(self, value):
246
247 if '_inData' in dir(self):
248 self._inData.update(value)
249 else:
250
251 self.inData = value
252
253
254 @property
255 def outData(self):
256
257 if '_outData' in dir(self):
258 return self._outData
259 return None
260
261 @outData.setter
262 def outData(self, value):
263 self._outData = set(value)
264
265 def outDataUpdate(self, value):
266
267 if '_outData' in dir(self):
268 self._outData.update(value)
269 else:
270
271 self.outData = value
272
273 @property
274
276 def input(self):
277
278 if '_input' in dir(self):
279 return self._input
280 return None
281
282 @property
283
285 def output(self):
286
287 if '_output' in dir(self):
288 return self._output
289 return None
290
291 @property
292 def extraMetadata(self):
293 return self._extraMetadata
294
295 @property
296 def hasExecuted(self):
297 return self._hasExecuted
298
299 @property
300 def rc(self):
301 return self._rc
302
303 @property
304 def errMsg(self):
305 return self._errMsg
306
307 @property
308 def validation(self):
309 return self._validation
310
311 @validation.setter
312 def validation(self, value):
313 self._validation = value
314
315 @property
316 def hasValidated(self):
317 return self._hasValidated
318
319 @property
320 def isValidated(self):
321 return self._isValidated
322
323
324 @property
325 def first(self):
326 if hasattr(self, '_first'):
327 return self._first
328 else:
329 return None
330
331 @property
333 return self._preExeStart
334
335 @property
336 def exeStartTimes(self):
337 return self._exeStart
338
339 @property
340 def exeStopTimes(self):
341 return self._exeStop
342
343 @property
344 def valStartTimes(self):
345 return self._valStart
346
347 @property
348 def valStopTimes(self):
349 return self._valStop
350
351 @property
352 def preExeCpuTime(self):
353 if self._preExeStart and self._exeStart:
354 return calcCpuTime(self._preExeStart, self._exeStart)
355 else:
356 return None
357
358 @property
359 def preExeWallTime(self):
360 if self._preExeStart and self._exeStart:
361 return calcWallTime(self._preExeStart, self._exeStart)
362 else:
363 return None
364
365 @property
366 def cpuTime(self):
367 if self._exeStart and self._exeStop:
368 return calcCpuTime(self._exeStart, self._exeStop)
369 else:
370 return None
371
372 @property
373 def usrTime(self):
374 if self._exeStart and self._exeStop:
375 return self._exeStop[2] - self._exeStart[2]
376 else:
377 return None
378
379 @property
380 def sysTime(self):
381 if self._exeStart and self._exeStop:
382 return self._exeStop[3] - self._exeStart[3]
383 else:
384 return None
385
386 @property
387 def wallTime(self):
388 if self._exeStart and self._exeStop:
389 return calcWallTime(self._exeStart, self._exeStop)
390 else:
391 return None
392
393 @property
394 def memStats(self):
395 return self._memStats
396
397 @property
398 def memAnalysis(self):
399 return self._memLeakResult
400
401 @property
402 def postExeCpuTime(self):
403 if self._exeStop and self._valStart:
404 return calcCpuTime(self._exeStop, self._valStart)
405 else:
406 return None
407
408 @property
410 if self._exeStop and self._valStart:
411 return calcWallTime(self._exeStop, self._valStart)
412 else:
413 return None
414
415 @property
417 if self._valStart and self._valStop:
418 return calcCpuTime(self._valStart, self._valStop)
419 else:
420 return None
421
422 @property
424 if self._valStart and self._valStop:
425 return calcWallTime(self._valStart, self._valStop)
426 else:
427 return None
428
429 @property
430 def cpuTimeTotal(self):
431 if self._preExeStart and self._valStop:
432 return calcCpuTime(self._preExeStart, self._valStop)
433 else:
434 return None
435
436 @property
437 def wallTimeTotal(self):
438 if self._preExeStart and self._valStop:
439 return calcWallTime(self._preExeStart, self._valStop)
440 else:
441 return None
442
443 @property
444 def eventCount(self):
445 return self._eventCount
446
447 @property
448 def reSimEvent(self):
449 return self._resimevents
450
451 @property
452 def athenaMP(self):
453 return self._athenaMP
454
455 @property
456 def dbMonitor(self):
457 return self._dbMonitor
458
459
460 # set start times, if not set already
461 def setPreExeStart(self):
462 if self._preExeStart is None:
463 self._preExeStart = os.times()
464 msg.debug('preExeStart time is {0}'.format(self._preExeStart))
465
466 def setValStart(self):
467 if self._valStart is None:
468 self._valStart = os.times()
469 msg.debug('valStart time is {0}'.format(self._valStart))
470
471 def preExecute(self, input = set(), output = set()):
472 self.setPreExeStart()
473 msg.info('Preexecute for %s', self._name)
474
475 def execute(self):
476 self._exeStart = os.times()
477 msg.debug('exeStart time is {0}'.format(self._exeStart))
478 msg.info('Starting execution of %s', self._name)
479 self._hasExecuted = True
480 self._rc = 0
481 self._errMsg = ''
482 msg.info('%s executor returns %d', self._name, self._rc)
483 self._exeStop = os.times()
484 msg.debug('preExeStop time is {0}'.format(self._exeStop))
485
486 def postExecute(self):
487 msg.info('Postexecute for %s', self._name)
488
489 def validate(self):
490 self.setValStart()
491 self._hasValidated = True
492 msg.info('Executor %s has no validation function - assuming all ok', self._name)
493 self._isValidated = True
494 self._errMsg = ''
495 self._valStop = os.times()
496 msg.debug('valStop time is {0}'.format(self._valStop))
497
498
499 def doAll(self, input=set(), output=set()):
500 self.preExecute(input, output)
501 self.execute()
502 self.postExecute()
503 self.validate()
504
505
507 def __init__(self, name = 'Logscan'):
508 super(logscanExecutor, self).__init__(name=name)
509 self._errorMaskFiles = None
510 self._logFileName = None
511
512 def preExecute(self, input = set(), output = set()):
513 self.setPreExeStart()
514 msg.info('Preexecute for %s', self._name)
515 if 'logfile' in self.conf.argdict:
516 self._logFileName = self.conf.argdict['logfile'].value
517
518 def validate(self):
519 self.setValStart()
520 msg.info("Starting validation for {0}".format(self._name))
521 if self._logFileName:
522
524 if 'ignorePatterns' in self.conf.argdict:
525 igPat = self.conf.argdict['ignorePatterns'].value
526 else:
527 igPat = []
528 if 'ignoreFiles' in self.conf.argdict:
529 ignorePatterns = trfValidation.ignorePatterns(files = self.conf.argdict['ignoreFiles'].value, extraSearch=igPat)
530 elif self._errorMaskFiles is not None:
531 ignorePatterns = trfValidation.ignorePatterns(files = self._errorMaskFiles, extraSearch=igPat)
532 else:
533 ignorePatterns = trfValidation.ignorePatterns(files = athenaExecutor._defaultIgnorePatternFile, extraSearch=igPat)
534
535 # Now actually scan my logfile
536 msg.info('Scanning logfile {0} for errors'.format(self._logFileName))
537 self._logScan = trfValidation.athenaLogFileReport(logfile = self._logFileName, ignoreList = ignorePatterns)
538 worstError = self._logScan.worstError()
539
540 # In general we add the error message to the exit message, but if it's too long then don't do
541 # that and just say look in the jobReport
542 if worstError['firstError']:
543 if len(worstError['firstError']['message']) > athenaExecutor._exitMessageLimit:
544 if 'CoreDumpSvc' in worstError['firstError']['message']:
545 exitErrorMessage = "Core dump at line {0} (see jobReport for further details)".format(worstError['firstError']['firstLine'])
546 elif 'G4Exception' in worstError['firstError']['message']:
547 exitErrorMessage = "G4 exception at line {0} (see jobReport for further details)".format(worstError['firstError']['firstLine'])
548 else:
549 exitErrorMessage = "Long {0} message at line {1} (see jobReport for further details)".format(worstError['level'], worstError['firstError']['firstLine'])
550 else:
551 exitErrorMessage = "Logfile error in {0}: \"{1}\"".format(self._logFileName, worstError['firstError']['message'])
552 else:
553 exitErrorMessage = "Error level {0} found (see athena logfile for details)".format(worstError['level'])
554
555 # Very simple: if we get ERROR or worse, we're dead, except if ignoreErrors=True
556 if worstError['nLevel'] == stdLogLevels['ERROR'] and ('ignoreErrors' in self.conf.argdict and self.conf.argdict['ignoreErrors'].value is True):
557 msg.warning('Found ERRORs in the logfile, but ignoring this as ignoreErrors=True (see jobReport for details)')
558 elif worstError['nLevel'] >= stdLogLevels['ERROR']:
559 self._isValidated = False
560 msg.error('Fatal error in athena logfile (level {0})'.format(worstError['level']))
561 raise trfExceptions.TransformLogfileErrorException(trfExit.nameToCode('TRF_EXEC_LOGERROR'),
562 'Fatal error in athena logfile: "{0}"'.format(exitErrorMessage))
563
564 # Must be ok if we got here!
565 msg.info('Executor {0} has validated successfully'.format(self.name))
566 self._isValidated = True
567 self._errMsg = ''
568
569 self._valStop = os.times()
570 msg.debug('valStop time is {0}'.format(self._valStop))
571
572
574 def __init__(self, name = 'Echo', trf = None):
575
576 # We are only changing the default name here
577 super(echoExecutor, self).__init__(name=name, trf=trf)
578
579
580 def execute(self):
581 self._exeStart = os.times()
582 msg.debug('exeStart time is {0}'.format(self._exeStart))
583 msg.info('Starting execution of %s', self._name)
584 msg.info('Transform argument dictionary now follows:')
585 for k, v in self.conf.argdict.items():
586 print("%s = %s" % (k, v))
587 self._hasExecuted = True
588 self._rc = 0
589 self._errMsg = ''
590 msg.info('%s executor returns %d', self._name, self._rc)
591 self._exeStop = os.times()
592 msg.debug('exeStop time is {0}'.format(self._exeStop))
593
594
596 def __init__(self, name = 'Dummy', trf = None, conf = None, inData = set(), outData = set()):
597
598 # We are only changing the default name here
599 super(dummyExecutor, self).__init__(name=name, trf=trf, conf=conf, inData=inData, outData=outData)
600
601
602 def execute(self):
603 self._exeStart = os.times()
604 msg.debug('exeStart time is {0}'.format(self._exeStart))
605 msg.info('Starting execution of %s', self._name)
606 for type in self._outData:
607 for k, v in self.conf.argdict.items():
608 if type in k:
609 msg.info('Creating dummy output file: {0}'.format(self.conf.argdict[k].value[0]))
610 open(self.conf.argdict[k].value[0], 'a').close()
611 self._hasExecuted = True
612 self._rc = 0
613 self._errMsg = ''
614 msg.info('%s executor returns %d', self._name, self._rc)
615 self._exeStop = os.times()
616 msg.debug('exeStop time is {0}'.format(self._exeStop))
617
618
620 def __init__(self, name = 'Script', trf = None, conf = None, inData = set(), outData = set(),
621 exe = None, exeArgs = None, memMonitor = True):
622 # Name of the script we want to execute
623 self._exe = exe
624
625 # With arguments (currently this means paste in the corresponding _argdict entry)
626 self._exeArgs = exeArgs
627
628 super(scriptExecutor, self).__init__(name=name, trf=trf, conf=conf, inData=inData, outData=outData)
629
630 self._extraMetadata.update({'script' : exe})
631
632 # Decide if we echo script output to stdout
633 self._echoOutput = False
634
635 # Can either be written by base class or child
636 self._cmd = None
637
638 self._memMonitor = memMonitor
639
640 @property
641 def exe(self):
642 return self._exe
643
644 @exe.setter
645 def exe(self, value):
646 self._exe = value
647 self._extraMetadata['script'] = value
648
649 @property
650 def exeArgs(self):
651 return self._exeArgs
652
653 @exeArgs.setter
654 def exeArgs(self, value):
655 self._exeArgs = value
656# self._extraMetadata['scriptArgs'] = value
657
658 def preExecute(self, input = set(), output = set()):
659 self.setPreExeStart()
660 msg.debug('scriptExecutor: Preparing for execution of {0} with inputs {1} and outputs {2}'.format(self.name, input, output))
661
662 self._input = input
663 self._output = output
664
665
666 if self._cmd is None:
668 msg.info('Will execute script as %s', self._cmd)
669
670 # Define this here to have it for environment detection messages
671 self._logFileName = "log.{0}".format(self._name)
672
673
675 if 'TRF_ECHO' in os.environ:
676 msg.info('TRF_ECHO envvar is set - enabling command echoing to stdout')
677 self._echoOutput = True
678 elif 'TRF_NOECHO' in os.environ:
679 msg.info('TRF_NOECHO envvar is set - disabling command echoing to stdout')
680 self._echoOutput = False
681 # PS1 is for sh, bash; prompt is for tcsh and zsh
682 elif isInteractiveEnv():
683 msg.info('Interactive environment detected (stdio or stdout is a tty) - enabling command echoing to stdout')
684 self._echoOutput = True
685 elif 'TZHOME' in os.environ:
686 msg.info('Tier-0 environment detected - enabling command echoing to stdout')
687 self._echoOutput = True
688 if self._echoOutput is False:
689 msg.info('Batch/grid running - command outputs will not be echoed. Logs for {0} are in {1}'.format(self._name, self._logFileName))
690
691 # Now setup special loggers for logging execution messages to stdout and file
692 self._echologger = logging.getLogger(self._name)
693 self._echologger.setLevel(logging.INFO)
694 self._echologger.propagate = False
695
696 encargs = {'encoding' : 'utf-8'}
697 self._exeLogFile = logging.FileHandler(self._logFileName, mode='w', **encargs)
698 self._exeLogFile.setFormatter(logging.Formatter('%(asctime)s %(message)s', datefmt='%H:%M:%S'))
699 self._echologger.addHandler(self._exeLogFile)
700
701 if self._echoOutput:
702 self._echostream = logging.StreamHandler(_encoding_stream(sys.stdout))
703 self._echostream.setFormatter(logging.Formatter('%(name)s %(asctime)s %(message)s', datefmt='%H:%M:%S'))
704 self._echologger.addHandler(self._echostream)
705
707 if self._exe:
708 self._cmd = [self.exe, ]
709 else:
710 raise trfExceptions.TransformExecutionException(trfExit.nameToCode('TRF_EXEC_SETUP_FAIL'),
711 'No executor set in {0}'.format(self.__class__.__name__))
712 for arg in self.exeArgs:
713 if arg in self.conf.argdict:
714 # If we have a list then add each element to our list, else just str() the argument value
715 # Note if there are arguments which need more complex transformations then
716 # consider introducing a special toExeArg() method.
717 if isinstance(self.conf.argdict[arg].value, list):
718 self._cmd.extend([ str(v) for v in self.conf.argdict[arg].value])
719 else:
720 self._cmd.append(str(self.conf.argdict[arg].value))
721
722
723 def execute(self):
724 self._hasExecuted = True
725 msg.info('Starting execution of {0} ({1})'.format(self._name, self._cmd))
726
727 self._exeStart = os.times()
728 msg.debug('exeStart time is {0}'.format(self._exeStart))
729 if ('execOnly' in self.conf.argdict and self.conf.argdict['execOnly'] is True):
730 msg.info('execOnly flag is set - execution will now switch, replacing the transform')
731 os.execvp(self._cmd[0], self._cmd)
732
733 encargs = {'encoding' : 'utf8'}
734 try:
735 # if we are already inside a container, then
736 # must map /srv of the nested container to /srv of the parent container:
737 # https://twiki.atlas-canada.ca/bin/view/AtlasCanada/Containers#Mount_point_summary
738 if self._alreadyInContainer and self._containerSetup is not None:
739 msg.info("chdir /srv to launch a nested container for the substep")
740 os.chdir("/srv")
741 p = subprocess.Popen(self._cmd, shell = False, stdout = subprocess.PIPE, stderr = subprocess.STDOUT, bufsize = 1, **encargs)
742 # go back to the original working directory immediately after to store prmon in it
743 if self._alreadyInContainer and self._containerSetup is not None:
744 msg.info("chdir {} after launching the nested container".format(self._workdir))
745 os.chdir(self._workdir)
746
747 if self._memMonitor:
748 try:
749 self._memSummaryFile = 'prmon.summary.' + self._name + '.json'
750 self._memFullFile = 'prmon.full.' + self._name
751 memMonitorCommand = ['prmon', '--pid', str(p.pid), '--filename', 'prmon.full.' + self._name,
752 '--json-summary', self._memSummaryFile, '--log-filename', 'prmon.' + self._name + '.log',
753 '--interval', '30']
754 mem_proc = subprocess.Popen(memMonitorCommand, shell = False, close_fds=True, **encargs)
755 # TODO - link mem.full.current to mem.full.SUBSTEP
756 except Exception as e:
757 msg.warning('Failed to spawn memory monitor for {0}: {1}'.format(self._name, e))
758 self._memMonitor = False
759
760 while p.poll() is None:
761 try:
762 line = p.stdout.readline()
763 if line:
764 self._echologger.info(line.rstrip())
765 except UnicodeDecodeError as e:
766 msg.warning('Exception raised processing athena log: {0}'.format(e))
767 # Hoover up remaining buffered output lines
768 for line in p.stdout:
769 self._echologger.info(line.rstrip())
770
771 self._rc = p.returncode
772 msg.info('%s executor returns %d', self._name, self._rc)
773 self._exeStop = os.times()
774 msg.debug('exeStop time is {0}'.format(self._exeStop))
775 except OSError as e:
776 errMsg = 'Execution of {0} failed and raised OSError: {1}'.format(self._cmd[0], e)
777 msg.error(errMsg)
778 raise trfExceptions.TransformExecutionException(trfExit.nameToCode('TRF_EXEC'), errMsg)
779 finally:
780 if self._memMonitor:
781 try:
782 mem_proc.send_signal(signal.SIGUSR1)
783 countWait = 0
784 while (not mem_proc.poll()) and countWait < 10:
785 time.sleep(0.1)
786 countWait += 1
787 except OSError:
788 pass
789
790
791 def postExecute(self):
792 if hasattr(self._exeLogFile, 'close'):
793 self._exeLogFile.close()
794 if self._memMonitor:
795 try:
796 memFile = open(self._memSummaryFile)
797 self._memStats = json.load(memFile)
798 except Exception as e:
799 msg.warning('Failed to load JSON memory summmary file {0}: {1}'.format(self._memSummaryFile, e))
800 self._memMonitor = False
801 self._memStats = {}
802
803
804 def validate(self):
805 if self._valStart is None:
806 self._valStart = os.times()
807 msg.debug('valStart time is {0}'.format(self._valStart))
808 self._hasValidated = True
809
810
811 if self._rc == 0:
812 msg.info('Executor {0} validated successfully (return code {1})'.format(self._name, self._rc))
813 self._isValidated = True
814 self._errMsg = ''
815 else:
816 # Want to learn as much as possible from the non-zero code
817 # this is a bit hard in general, although one can do signals.
818 # Probably need to be more specific per exe, i.e., athena non-zero codes
819 self._isValidated = False
820 if self._rc < 0:
821 # Map return codes to what the shell gives (128 + SIGNUM)
822 self._rc = 128 - self._rc
823 if trfExit.codeToSignalname(self._rc) != "":
824 self._errMsg = '{0} got a {1} signal (exit code {2})'.format(self._name, trfExit.codeToSignalname(self._rc), self._rc)
825 else:
826 self._errMsg = 'Non-zero return code from %s (%d)' % (self._name, self._rc)
827 raise trfExceptions.TransformValidationException(trfExit.nameToCode('TRF_EXEC_FAIL'), self._errMsg)
828
829
831 if 'checkEventCount' in self.conf.argdict and self.conf.argdict['checkEventCount'].returnMyValue(exe=self) is False:
832 msg.info('Event counting for substep {0} is skipped'.format(self.name))
833 else:
834 if 'mpi' in self.conf.argdict and self.conf.argdict['mpi'].value and not mpi.mpiShouldValidate():
835 msg.info('MPI mode -- skipping output event count check')
836 else:
837 checkcount=trfValidation.eventMatch(self)
838 checkcount.decide()
839 self._eventCount = checkcount.eventCount
840 msg.info('Event counting for substep {0} passed'.format(self.name))
841
842 self._valStop = os.times()
843 msg.debug('valStop time is {0}'.format(self._valStop))
844
845
846
848 _exitMessageLimit = 200 # Maximum error message length to report in the exitMsg
849 _defaultIgnorePatternFile = ['atlas_error_mask.db']
850
851
888 def __init__(self, name = 'athena', trf = None, conf = None, skeletonFile=None, skeletonCA=None,
889 inData = set(), outData = set(), inputDataTypeCountCheck = None, exe = 'athena.py', exeArgs = ['athenaopts'],
890 substep = None, inputEventTest = True, perfMonFile = None, tryDropAndReload = True, extraRunargs = {}, runtimeRunargs = {},
891 literalRunargs = [], dataArgs = [], checkEventCount = False, errorMaskFiles = None,
892 manualDataDictionary = None, memMonitor = True, disableMT = False, disableMP = False, onlyMP = False, onlyMT = False, onlyMPWithRunargs = None):
893
894 self._substep = forceToAlphaNum(substep)
895 self._inputEventTest = inputEventTest
896 self._tryDropAndReload = tryDropAndReload
897 self._extraRunargs = extraRunargs
898 self._runtimeRunargs = runtimeRunargs
899 self._literalRunargs = literalRunargs
900 self._dataArgs = dataArgs
901 self._errorMaskFiles = errorMaskFiles
902 self._inputDataTypeCountCheck = inputDataTypeCountCheck
903 self._disableMT = disableMT
904 self._disableMP = disableMP
905 self._onlyMP = onlyMP
906 self._onlyMT = onlyMT
907 self._onlyMPWithRunargs = onlyMPWithRunargs
908 self._skeletonCA=skeletonCA
909
910 if perfMonFile:
911 self._perfMonFile = None
912 msg.debug("Resource monitoring from PerfMon is now deprecated")
913
914 # SkeletonFile can be None (disable) or a string or a list of strings - normalise it here
915 if isinstance(skeletonFile, str):
916 self._skeleton = [skeletonFile]
917 else:
918 self._skeleton = skeletonFile
919
920 super(athenaExecutor, self).__init__(name=name, trf=trf, conf=conf, inData=inData, outData=outData, exe=exe,
921 exeArgs=exeArgs, memMonitor=memMonitor)
922
923 # Add athena specific metadata
924 self._extraMetadata.update({'substep': substep})
925
926 # Setup JO templates
927 if self._skeleton or self._skeletonCA:
928 self._jobOptionsTemplate = JobOptionsTemplate(exe = self, version = '$Id: trfExe.py 792052 2017-01-13 13:36:51Z mavogel $')
929 else:
930 self._jobOptionsTemplate = None
931
932 @property
934 return self._inputDataTypeCountCheck
935
936 @inputDataTypeCountCheck.setter
937 def inputDataTypeCountCheck(self, value):
938 self._inputDataTypeCountCheck = value
939
940 @property
941 def substep(self):
942 return self._substep
943
944 @property
945 def disableMP(self):
946 return self._disableMP
947
948 @disableMP.setter
949 def disableMP(self, value):
950 self._disableMP = value
951
952 @property
953 def disableMT(self):
954 return self._disableMT
955
956 @disableMT.setter
957 def disableMT(self, value):
958 self._disableMT = value
959
960 @property
961 def onlyMP(self):
962 return self._onlyMP
963
964 @onlyMP.setter
965 def onlyMP(self, value):
966 self._onlyMP = value
967
968 @property
969 def onlyMT(self):
970 return self._onlyMT
971
972 @onlyMT.setter
973 def onlyMT(self, value):
974 self._onlyMT = value
975
976 def skeletonCA(self):
977 return self._skeletonCA
978
979 def preExecute(self, input = set(), output = set()):
980 self.setPreExeStart()
981 msg.debug('Preparing for execution of {0} with inputs {1} and outputs {2}'.format(self.name, input, output))
982
983 # Check we actually have events to process!
984 inputEvents = 0
985 dt = ""
986 if self._inputDataTypeCountCheck is None:
987 self._inputDataTypeCountCheck = input
988 for dataType in self._inputDataTypeCountCheck:
989 if self.conf.dataDictionary[dataType].nentries == 'UNDEFINED':
990 continue
991
992 thisInputEvents = self.conf.dataDictionary[dataType].nentries
993 if thisInputEvents > inputEvents:
994 inputEvents = thisInputEvents
995 dt = dataType
996
997 # Now take into account skipEvents and maxEvents
998 if ('skipEvents' in self.conf.argdict and
999 self.conf.argdict['skipEvents'].returnMyValue(name=self._name, substep=self._substep, first=self.conf.firstExecutor) is not None):
1000 mySkipEvents = self.conf.argdict['skipEvents'].returnMyValue(name=self._name, substep=self._substep, first=self.conf.firstExecutor)
1001 else:
1002 mySkipEvents = 0
1003
1004 if ('maxEvents' in self.conf.argdict and
1005 self.conf.argdict['maxEvents'].returnMyValue(name=self._name, substep=self._substep, first=self.conf.firstExecutor) is not None):
1006 myMaxEvents = self.conf.argdict['maxEvents'].returnMyValue(name=self._name, substep=self._substep, first=self.conf.firstExecutor)
1007 else:
1008 myMaxEvents = -1
1009
1010 # Any events to process...?
1011 if (self._inputEventTest and mySkipEvents > 0 and mySkipEvents >= inputEvents):
1012 raise trfExceptions.TransformExecutionException(trfExit.nameToCode('TRF_NOEVENTS'),
1013 'No events to process: {0} (skipEvents) >= {1} (inputEvents of {2}'.format(mySkipEvents, inputEvents, dt))
1014
1015 try:
1016 # Expected events to process
1017 if (myMaxEvents != -1):
1018 if (self.inData and next(iter(self.inData)) == 'inNULL'):
1019 expectedEvents = myMaxEvents
1020 else:
1021 expectedEvents = min(inputEvents-mySkipEvents, myMaxEvents)
1022 else:
1023 expectedEvents = inputEvents-mySkipEvents
1024 except TypeError:
1025 # catching type error from UNDEFINED inputEvents count
1026 msg.info('input event count is UNDEFINED, setting expectedEvents to 0')
1027 expectedEvents = 0
1028
1029
1032 OSSetupString = None
1033
1034 # Extract the asetup string
1035 asetupString = None
1036 legacyThreadingRelease = False
1037 if 'asetup' in self.conf.argdict:
1038 asetupString = self.conf.argdict['asetup'].returnMyValue(name=self._name, substep=self._substep, first=self.conf.firstExecutor)
1039 legacyThreadingRelease = asetupReleaseIsOlderThan(asetupString, 22)
1040 else:
1041 msg.info('Asetup report: {0}'.format(asetupReport()))
1042
1043 if asetupString is not None:
1044 legacyOSRelease = asetupReleaseIsOlderThan(asetupString, 24)
1045 currentOS = os.environ['ALRB_USER_PLATFORM']
1046 if legacyOSRelease and "centos7" not in currentOS:
1047 OSSetupString = "centos7"
1048 msg.info('Legacy release required for the substep {}, will setup a container running {}'.format(self._substep, OSSetupString))
1049
1050
1051 # allow overriding the container OS using a flag
1052 if 'runInContainer' in self.conf.argdict:
1053 OSSetupString = self.conf.argdict['runInContainer'].returnMyValue(name=self._name, substep=self._substep, first=self.conf.firstExecutor)
1054 msg.info('The step {} will be performed in a container running {}, as explicitly requested'.format(self._substep, OSSetupString))
1055 if OSSetupString is not None and asetupString is None:
1056 raise trfExceptions.TransformExecutionException(trfExit.nameToCode('TRF_EXEC_SETUP_FAIL'),
1057 '--asetup must be used for the substep which requires --runInContainer')
1058
1059 # Conditional MP based on runtime arguments
1060 if self._onlyMPWithRunargs:
1061 for k in self._onlyMPWithRunargs:
1062 if k in self.conf._argdict:
1063 self._onlyMP = True
1064
1065
1066 # Check the consistency of parallel configuration: CLI flags + evnironment.
1067 if (('multiprocess' in self.conf._argdict and self.conf._argdict['multiprocess'].value) and
1068 ('ATHENA_CORE_NUMBER' not in os.environ)):
1069 raise ValueError('--multiprocess argument used but ATHENA_CORE_NUMBER environment variable not set. Please check your configuration!')
1070
1071
1072 else:
1073 # Try to detect AthenaMT mode, number of threads and number of concurrent events
1074 if not self._disableMT:
1075 if ('multithreaded' in self.conf._argdict and self.conf._argdict['multithreaded'].value) and ('ATHENA_CORE_NUMBER' not in os.environ):
1076 msg.warning('--multithreaded argument used but ATHENA_CORE_NUMBER environment variable not set. Athena will continue in MT mode with 1 thread')
1077 self._athenaMT = 1
1079 else:
1080 self._athenaMT, self._athenaConcurrentEvents = detectAthenaMTThreads(self.conf.argdict, self.name, legacyThreadingRelease)
1081
1082 # Try to detect AthenaMP mode and number of workers
1083 if not self._disableMP:
1084 self._athenaMP = detectAthenaMPProcs(self.conf.argdict, self.name, legacyThreadingRelease)
1085
1086 # Check that we actually support MT
1087 if self._onlyMP and self._athenaMT > 0:
1088 msg.info("This configuration does not support MT, falling back to MP")
1089 if self._athenaMP == 0:
1090 self._athenaMP = self._athenaMT
1091 self._athenaMT = 0
1093
1094 # Check that we actually support MP
1095 if self._onlyMT and self._athenaMP > 0:
1096 msg.info("This configuration does not support MP, using MT")
1097 if self._athenaMT == 0:
1098 self._athenaMT = self._athenaMP
1100 self._athenaMP = 0
1101
1102 # Small hack to detect cases where there are so few events that it's not worthwhile running in MP mode
1103 # which also avoids issues with zero sized files. Distinguish from the no-input case (e.g. evgen)
1104 if not self._disableMP and expectedEvents < self._athenaMP and not self._inData=={'inNULL'}:
1105 msg.info("Disabling AthenaMP as number of input events to process is too low ({0} events for {1} workers)".format(expectedEvents, self._athenaMP))
1106 self._disableMP = True
1107 self._athenaMP = 0
1108
1109 # Handle executor steps
1110 if self.conf.totalExecutorSteps > 1:
1111 for dataType in output:
1112 if self.conf._dataDictionary[dataType].originalName:
1113 self.conf._dataDictionary[dataType].value[0] = self.conf._dataDictionary[dataType].originalName
1114 else:
1115 self.conf._dataDictionary[dataType].originalName = self.conf._dataDictionary[dataType].value[0]
1116 self.conf._dataDictionary[dataType].value[0] += "_{0}{1}".format(executorStepSuffix, self.conf.executorStep)
1117 msg.info("Updated athena output filename for {0} to {1}".format(dataType, self.conf._dataDictionary[dataType].value[0]))
1118
1119 # And if this is (still) athenaMP, then set some options for workers and output file report
1120 if self._athenaMP > 0:
1121 self._athenaMPWorkerTopDir = 'athenaMP-workers-{0}-{1}'.format(self._name, self._substep)
1122 self._athenaMPFileReport = 'athenaMP-outputs-{0}-{1}'.format(self._name, self._substep)
1123 self._athenaMPEventOrdersFile = 'athenamp_eventorders.txt.{0}'.format(self._name)
1124 if 'athenaMPUseEventOrders' in self.conf.argdict and self.conf._argdict['athenaMPUseEventOrders'].value is True:
1126 else:
1127 self._athenaMPReadEventOrders = False
1128 # Decide on scheduling
1129 if ('athenaMPStrategy' in self.conf.argdict and
1130 (self.conf.argdict['athenaMPStrategy'].returnMyValue(name=self._name, substep=self._substep, first=self.conf.firstExecutor) is not None)):
1131 self._athenaMPStrategy = self.conf.argdict['athenaMPStrategy'].returnMyValue(name=self._name, substep=self._substep, first=self.conf.firstExecutor)
1132 else:
1133 self._athenaMPStrategy = 'SharedQueue'
1134
1135 # See if we have options for the target output file size
1136 if 'athenaMPMergeTargetSize' in self.conf.argdict:
1137 for dataType in output:
1138 if dataType in self.conf.argdict['athenaMPMergeTargetSize'].value:
1139 self.conf._dataDictionary[dataType].mergeTargetSize = self.conf.argdict['athenaMPMergeTargetSize'].value[dataType] * 1000000 # Convert from MB to B
1140 msg.info('Set target merge size for {0} to {1}'.format(dataType, self.conf._dataDictionary[dataType].mergeTargetSize))
1141 else:
1142 # Use a globbing strategy
1143 matchedViaGlob = False
1144 for mtsType, mtsSize in self.conf.argdict['athenaMPMergeTargetSize'].value.items():
1145 if fnmatch(dataType, mtsType):
1146 self.conf._dataDictionary[dataType].mergeTargetSize = mtsSize * 1000000 # Convert from MB to B
1147 msg.info('Set target merge size for {0} to {1} from "{2}" glob'.format(dataType, self.conf._dataDictionary[dataType].mergeTargetSize, mtsType))
1148 matchedViaGlob = True
1149 break
1150 if not matchedViaGlob and "ALL" in self.conf.argdict['athenaMPMergeTargetSize'].value:
1151 self.conf._dataDictionary[dataType].mergeTargetSize = self.conf.argdict['athenaMPMergeTargetSize'].value["ALL"] * 1000000 # Convert from MB to B
1152 msg.info('Set target merge size for {0} to {1} from "ALL" value'.format(dataType, self.conf._dataDictionary[dataType].mergeTargetSize))
1153
1154 # For AthenaMP jobs we ensure that the athena outputs get the suffix _000
1155 # so that the mother process output file (if it exists) can be used directly
1156 # as soft linking can lead to problems in the PoolFileCatalog (see ATLASJT-317)
1157 for dataType in output:
1158 if self.conf.totalExecutorSteps <= 1:
1159 self.conf._dataDictionary[dataType].originalName = self.conf._dataDictionary[dataType].value[0]
1160 if 'eventService' not in self.conf.argdict or 'eventService' in self.conf.argdict and self.conf.argdict['eventService'].value is False:
1161 if 'sharedWriter' in self.conf.argdict and self.conf.argdict['sharedWriter'].value:
1162 msg.info("SharedWriter: not updating athena output filename for {0}".format(dataType))
1163 else:
1164 self.conf._dataDictionary[dataType].value[0] += "_000"
1165 msg.info("Updated athena output filename for {0} to {1}".format(dataType, self.conf._dataDictionary[dataType].value[0]))
1166 else:
1168
1169
1170 if 'mpi' in self.conf.argdict and self.conf.argdict['mpi'].value:
1171 msg.info("Running in MPI mode")
1172 mpi.setupMPIConfig(output, self.conf.dataDictionary)
1173
1174
1175 if self._skeleton or self._skeletonCA:
1176 inputFiles = dict()
1177 for dataType in input:
1178 inputFiles[dataType] = self.conf.dataDictionary[dataType]
1179 outputFiles = dict()
1180 for dataType in output:
1181 outputFiles[dataType] = self.conf.dataDictionary[dataType]
1182
1183 # See if we have any 'extra' file arguments
1184 nameForFiles = commonExecutorStepName(self._name)
1185 for dataType, dataArg in self.conf.dataDictionary.items():
1186 if isinstance(dataArg, list) and dataArg:
1187 if self.conf.totalExecutorSteps <= 1:
1188 raise ValueError('Multiple input arguments provided but only running one substep')
1189 if self.conf.totalExecutorSteps != len(dataArg):
1190 raise ValueError(f'{len(dataArg)} input arguments provided but running {self.conf.totalExecutorSteps} substeps')
1191
1192 if dataArg[self.conf.executorStep].io == 'input' and nameForFiles in dataArg[self.conf.executorStep].executor:
1193 inputFiles[dataArg[self.conf.executorStep].subtype] = dataArg
1194 else:
1195 if dataArg.io == 'input' and nameForFiles in dataArg.executor:
1196 inputFiles[dataArg.subtype] = dataArg
1197
1198 msg.debug('Input Files: {0}; Output Files: {1}'.format(inputFiles, outputFiles))
1199
1200 # Get the list of top options files that will be passed to athena (=runargs file + all skeletons)
1201 self._topOptionsFiles = self._jobOptionsTemplate.getTopOptions(input = inputFiles,
1202 output = outputFiles)
1203
1204
1206 if len(input) > 0:
1207 self._extraMetadata['inputs'] = list(input)
1208 if len(output) > 0:
1209 self._extraMetadata['outputs'] = list(output)
1210
1211
1212 dbrelease = dbsetup = None
1213 if 'DBRelease' in self.conf.argdict:
1214 dbrelease = self.conf.argdict['DBRelease'].returnMyValue(name=self._name, substep=self._substep, first=self.conf.firstExecutor)
1215 if path.islink(dbrelease):
1216 dbrelease = path.realpath(dbrelease)
1217 if dbrelease:
1218 # Classic tarball - filename format is DBRelease-X.Y.Z.tar.gz
1219 dbdMatch = re.match(r'DBRelease-([\d\.]+)\.tar\.gz', path.basename(dbrelease))
1220 if dbdMatch:
1221 msg.debug('DBRelease setting {0} matches classic tarball file'.format(dbrelease))
1222 if not os.access(dbrelease, os.R_OK):
1223 msg.warning('Transform was given tarball DBRelease file {0}, but this is not there'.format(dbrelease))
1224 msg.warning('I will now try to find DBRelease {0} in cvmfs'.format(dbdMatch.group(1)))
1225 dbrelease = dbdMatch.group(1)
1226 dbsetup = cvmfsDBReleaseCheck(dbrelease)
1227 else:
1228 # Check if the DBRelease is setup
1229 msg.debug('Setting up {0} from {1}'.format(dbdMatch.group(1), dbrelease))
1230 unpacked, dbsetup = unpackDBRelease(tarball=dbrelease, dbversion=dbdMatch.group(1))
1231 if unpacked:
1232 # Now run the setup.py script to customise the paths to the current location...
1233 setupDBRelease(dbsetup)
1234 # For cvmfs we want just the X.Y.Z release string (and also support 'current')
1235 else:
1236 dbsetup = cvmfsDBReleaseCheck(dbrelease)
1237
1238
1240 self._envUpdate.setStandardEnvironment(self.conf.argdict, name=self.name, substep=self.substep)
1242
1243
1244 super(athenaExecutor, self).preExecute(input, output)
1245
1246 # Now we always write a wrapper, because it's very convenient for re-running individual substeps
1247 # This will have asetup and/or DB release setups in it
1248 # Do this last in this preExecute as the _cmd needs to be finalised
1249 msg.info('Now writing wrapper for substep executor {0}'.format(self._name))
1250 self._writeAthenaWrapper(asetup=asetupString, dbsetup=dbsetup, ossetup=OSSetupString)
1251 msg.info('Athena will be executed in a subshell via {0}'.format(self._cmd))
1252
1253
1254 def postExecute(self):
1255 super(athenaExecutor, self).postExecute()
1256 # MPI merging
1257 if 'mpi' in self.conf.argdict and self.conf.argdict['mpi'].value:
1258 mpi.mergeOutputs()
1259
1260 # Handle executor substeps
1261 if self.conf.totalExecutorSteps > 1:
1262 if self._athenaMP > 0:
1263 outputDataDictionary = dict([ (dataType, self.conf.dataDictionary[dataType]) for dataType in self._output ])
1264 athenaMPOutputHandler(self._athenaMPFileReport, self._athenaMPWorkerTopDir, outputDataDictionary, self._athenaMP, False, self.conf.argdict)
1265 if self.conf.executorStep == self.conf.totalExecutorSteps - 1:
1266 # first loop over datasets for the output
1267 for dataType in self._output:
1268 newValue = []
1269 if self._athenaMP > 0:
1270 # assume the same number of workers all the time
1271 for i in range(self.conf.totalExecutorSteps):
1272 for v in self.conf.dataDictionary[dataType].value:
1273 newValue.append(v.replace('_{0}{1}_'.format(executorStepSuffix, self.conf.executorStep),
1274 '_{0}{1}_'.format(executorStepSuffix, i)))
1275 else:
1276 self.conf.dataDictionary[dataType].multipleOK = True
1277 # just combine all executors
1278 for i in range(self.conf.totalExecutorSteps):
1279 newValue.append(self.conf.dataDictionary[dataType].originalName + '_{0}{1}'.format(executorStepSuffix, i))
1280 self.conf.dataDictionary[dataType].value = newValue
1281
1282 # do the merging if needed
1283 if self.conf.dataDictionary[dataType].io == "output" and len(self.conf.dataDictionary[dataType].value) > 1:
1284 self._smartMerge(self.conf.dataDictionary[dataType])
1285
1286 # If this was an athenaMP run then we need to update output files
1287 elif self._athenaMP > 0:
1288 outputDataDictionary = dict([ (dataType, self.conf.dataDictionary[dataType]) for dataType in self._output ])
1289
1290 skipFileChecks=False
1291 if 'eventService' in self.conf.argdict and self.conf.argdict['eventService'].value:
1292 skipFileChecks=True
1293 athenaMPOutputHandler(self._athenaMPFileReport, self._athenaMPWorkerTopDir, outputDataDictionary, self._athenaMP, skipFileChecks, self.conf.argdict)
1294 for dataType in self._output:
1295 if self.conf.dataDictionary[dataType].io == "output" and len(self.conf.dataDictionary[dataType].value) > 1:
1296 self._smartMerge(self.conf.dataDictionary[dataType])
1297
1298 if 'TXT_JIVEXMLTGZ' in self.conf.dataDictionary:
1299 self._targzipJiveXML()
1300
1301 # Summarise events passed the filter ISF_SimEventFilter from log.ReSim
1302 # This is a bit ugly to have such a specific feature here though
1303 # TODO
1304 # The best is to have a general approach so that user can extract useful info from log
1305 # Instead of hard coding a pattern, one idea could be that user provides a regExp pattern
1306 # in which the wanted variable is grouped by a name, then transforms could decode the pattern
1307 # and use it to extract required info and do the summation during log scan.
1308 if self._logFileName=='log.ReSim' and self.name=='ReSim':
1309 msg.info('scanning {0} for reporting events passed the filter ISF_SimEventFilter'.format(self._logFileName))
1310 self._resimevents = reportEventsPassedSimFilter(self._logFileName)
1311
1312 # Remove intermediate input/output files of sub-steps
1313 # Delete only files with io="temporay" which are files with pattern "tmp*"
1314 # Some stubs like tmp.RDO_TRIG_000 created in AthenaMP mode or
1315 # tmp.HIST_ESD_INT, tmp.HIST_AOD_INT as input to DQHistogramMerge.py are not deleted
1316 # Enable if --deleteIntermediateOutputfiles is set
1317 if ('deleteIntermediateOutputfiles' in self.conf._argdict and self.conf._argdict['deleteIntermediateOutputfiles'].value):
1318 inputDataDictionary = dict([ (dataType, self.conf.dataDictionary[dataType]) for dataType in self._input ])
1319
1320 for k, v in inputDataDictionary.items():
1321 if not v.io == 'temporary':
1322 continue
1323 for filename in v.value:
1324 if os.access(filename, os.R_OK) and not filename.startswith("/cvmfs"):
1325 msg.info("Removing intermediate {0} input file {1}".format(k, filename))
1326 # Check if symbolic link and delete also linked file
1327 if (os.path.realpath(filename) != filename):
1328 targetpath = os.path.realpath(filename)
1329 os.unlink(filename)
1330 if (targetpath) and os.access(targetpath, os.R_OK):
1331 os.unlink(targetpath)
1332
1333
1334 def validate(self):
1335 self.setValStart()
1336 self._hasValidated = True
1337 deferredException = None
1338 memLeakThreshold = 5000
1339 _hasMemLeak = False
1340
1341
1342 try:
1343 super(athenaExecutor, self).validate()
1345 # In this case we hold this exception until the logfile has been scanned
1346 msg.error('Validation of return code failed: {0!s}'.format(e))
1347 deferredException = e
1348
1349
1357 msg.info('Analysing memory monitor output file {0} for possible memory leak'.format(self._memFullFile))
1358 self._memLeakResult = analytic().getFittedData(self._memFullFile)
1359 if self._memLeakResult:
1360 if self._memLeakResult['slope'] > memLeakThreshold:
1361 _hasMemLeak = True
1362 msg.warning('Possible memory leak; abnormally high values in memory monitor parameters (ignore this message if the job has finished successfully)')
1363 else:
1364 msg.warning('Failed to analyse the memory monitor file {0}'.format(self._memFullFile))
1365 else:
1366 msg.info('No memory monitor file to be analysed')
1367
1368 # Logfile scan setup
1369 # Always use ignorePatterns from the command line
1370 # For patterns in files, pefer the command line first, then any special settings for
1371 # this executor, then fallback to the standard default (atlas_error_mask.db)
1372 if 'ignorePatterns' in self.conf.argdict:
1373 igPat = self.conf.argdict['ignorePatterns'].value
1374 else:
1375 igPat = []
1376 if 'ignoreFiles' in self.conf.argdict:
1377 ignorePatterns = trfValidation.ignorePatterns(files = self.conf.argdict['ignoreFiles'].value, extraSearch=igPat)
1378 elif self._errorMaskFiles is not None:
1379 ignorePatterns = trfValidation.ignorePatterns(files = self._errorMaskFiles, extraSearch=igPat)
1380 else:
1381 ignorePatterns = trfValidation.ignorePatterns(files = athenaExecutor._defaultIgnorePatternFile, extraSearch=igPat)
1382
1383 # Now actually scan my logfile
1384 msg.info('Scanning logfile {0} for errors in substep {1}'.format(self._logFileName, self._substep))
1386 ignoreList=ignorePatterns)
1387 worstError = self._logScan.worstError()
1388 eventLoopWarnings = self._logScan.eventLoopWarnings()
1390
1391
1392 # In general we add the error message to the exit message, but if it's too long then don't do
1393 # that and just say look in the jobReport
1394 if worstError['firstError']:
1395 if len(worstError['firstError']['message']) > athenaExecutor._exitMessageLimit:
1396 if 'CoreDumpSvc' in worstError['firstError']['message']:
1397 exitErrorMessage = "Core dump at line {0} (see jobReport for further details)".format(worstError['firstError']['firstLine'])
1398 elif 'G4Exception' in worstError['firstError']['message']:
1399 exitErrorMessage = "G4 exception at line {0} (see jobReport for further details)".format(worstError['firstError']['firstLine'])
1400 else:
1401 exitErrorMessage = "Long {0} message at line {1} (see jobReport for further details)".format(worstError['level'], worstError['firstError']['firstLine'])
1402 else:
1403 exitErrorMessage = "Logfile error in {0}: \"{1}\"".format(self._logFileName, worstError['firstError']['message'])
1404 else:
1405 exitErrorMessage = "Error level {0} found (see athena logfile for details)".format(worstError['level'])
1406
1407 # If we failed on the rc, then abort now
1408 if deferredException is not None:
1409 # Add any logfile information we have
1410 if worstError['nLevel'] >= stdLogLevels['ERROR']:
1411 deferredException.errMsg = deferredException.errMsg + "; {0}".format(exitErrorMessage)
1412 # Add the result of memory analysis
1413 if _hasMemLeak:
1414 deferredException.errMsg = deferredException.errMsg + "; Possible memory leak: 'pss' slope: {0} KB/s".format(self._memLeakResult['slope'])
1415 raise deferredException
1416
1417
1418 # Very simple: if we get ERROR or worse, we're dead, except if ignoreErrors=True
1419 if worstError['nLevel'] == stdLogLevels['ERROR'] and ('ignoreErrors' in self.conf.argdict and self.conf.argdict['ignoreErrors'].value is True):
1420 msg.warning('Found ERRORs in the logfile, but ignoring this as ignoreErrors=True (see jobReport for details)')
1421 # Act as if ignoreErrors=True if running in MPI, because we want to be tolerant to the occasional event failure
1422 elif worstError['nLevel'] >= stdLogLevels['ERROR'] and (not mpi.mpiShouldValidate()):
1423 msg.warning(f'Found {worstError["level"]} in the logfile in MPI rank {mpi.getMPIRank()} but moving on to be failure-tolerant')
1424 elif worstError['nLevel'] >= stdLogLevels['ERROR']:
1425 self._isValidated = False
1426 msg.error('Fatal error in athena logfile (level {0})'.format(worstError['level']))
1427 # Add the result of memory analysis
1428 if _hasMemLeak:
1429 exitErrorMessage = exitErrorMessage + "; Possible memory leak: 'pss' slope: {0} KB/s".format(self._memLeakResult['slope'])
1430 raise trfExceptions.TransformLogfileErrorException(trfExit.nameToCode('TRF_EXEC_LOGERROR'),
1431 'Fatal error in athena logfile: "{0}"'.format(exitErrorMessage))
1432
1433 # Print event loop warnings
1434 if (len(eventLoopWarnings) > 0):
1435 msg.warning('Found WARNINGS in the event loop, as follows:')
1436 for element in eventLoopWarnings:
1437 msg.warning('{0} {1} ({2} instances)'.format(element['item']['service'],element['item']['message'],element['count']))
1438
1439 # Must be ok if we got here!
1440 msg.info('Executor {0} has validated successfully'.format(self.name))
1441 self._isValidated = True
1442
1443 self._valStop = os.times()
1444 msg.debug('valStop time is {0}'.format(self._valStop))
1445
1446
1447 def _isCAEnabled(self):
1448 # CA is now enabled by default, except for Gen_tf.py
1449 if self._name != 'generate' and self._name != 'afterburn':
1450 if 'CA' in self.conf.argdict and (self.conf.argdict['CA'] is False or self.conf.argdict['CA'].returnMyValue(name=self.name, substep=self.substep) is False):
1451 return False
1452 else:
1453 return True
1454 # the old logic is kept for Gen_tf.py, to be dropped once migrated to CA
1455 else:
1456 # CA not present
1457 if 'CA' not in self.conf.argdict:
1458 # If there is no legacy skeleton, then we are running with CA
1459 if not self._skeleton:
1460 return True
1461 else:
1462 return False
1463
1464 # CA present but None, all substeps running with CA
1465 if self.conf.argdict['CA'] is None:
1466 return True
1467
1468 # CA enabled for a substep, running with CA
1469 if self.conf.argdict['CA'].returnMyValue(name=self.name, substep=self.substep) is True:
1470 return True
1471
1472 return False
1473
1474
1476
1479 if 'athena' in self.conf.argdict:
1480 self._exe = self.conf.argdict['athena'].value
1481 self._cmd = [self._exe]
1482
1483 # Find options for the current substep. Name is prioritised (e.g. RAWtoALL) over alias (e.g. r2a). Last look for 'all'
1484 currentSubstep = None
1485 if 'athenaopts' in self.conf.argdict:
1486 currentName = commonExecutorStepName(self.name)
1487 if currentName in self.conf.argdict['athenaopts'].value:
1488 currentSubstep = currentName
1489 if self.substep in self.conf.argdict['athenaopts'].value:
1490 msg.info('Athenaopts found for {0} and {1}, joining options. '
1491 'Consider changing your configuration to use just the name or the alias of the substep.'
1492 .format(currentSubstep, self.substep))
1493 self.conf.argdict['athenaopts'].value[currentSubstep].extend(self.conf.argdict['athenaopts'].value[self.substep])
1494 del self.conf.argdict['athenaopts'].value[self.substep]
1495 msg.debug('Athenaopts: {0}'.format(self.conf.argdict['athenaopts'].value))
1496 elif self.substep in self.conf.argdict['athenaopts'].value:
1497 currentSubstep = self.substep
1498 elif 'all' in self.conf.argdict['athenaopts'].value:
1499 currentSubstep = 'all'
1500
1501 # See if there's a preloadlibs and a request to update LD_PRELOAD for athena
1502 preLoadUpdated = dict()
1503 if 'LD_PRELOAD' in self._envUpdate._envdict:
1504 preLoadUpdated[currentSubstep] = False
1505 if 'athenaopts' in self.conf.argdict:
1506 if currentSubstep is not None:
1507 for athArg in self.conf.argdict['athenaopts'].value[currentSubstep]:
1508 # This code is pretty ugly as the athenaopts argument contains
1509 # strings which are really key/value pairs
1510 if athArg.startswith('--preloadlib'):
1511 try:
1512 i = self.conf.argdict['athenaopts'].value[currentSubstep].index(athArg)
1513 v = athArg.split('=', 1)[1]
1514 msg.info('Updating athena --preloadlib option for substep {1} with: {0}'.format(self._envUpdate.value('LD_PRELOAD'), self.name))
1515 newPreloads = ":".join(set(v.split(":")) | set(self._envUpdate.value('LD_PRELOAD').split(":")))
1516 self.conf.argdict['athenaopts']._value[currentSubstep][i] = '--preloadlib={0}'.format(newPreloads)
1517 except Exception as e:
1518 msg.warning('Failed to interpret athena option: {0} ({1})'.format(athArg, e))
1519 preLoadUpdated[currentSubstep] = True
1520 break
1521 if not preLoadUpdated[currentSubstep]:
1522 msg.info('Setting athena preloadlibs for substep {1} to: {0}'.format(self._envUpdate.value('LD_PRELOAD'), self.name))
1523 if 'athenaopts' in self.conf.argdict:
1524 if currentSubstep is not None:
1525 self.conf.argdict['athenaopts'].value[currentSubstep].append("--preloadlib={0}".format(self._envUpdate.value('LD_PRELOAD')))
1526 else:
1527 self.conf.argdict['athenaopts'].value['all'] = ["--preloadlib={0}".format(self._envUpdate.value('LD_PRELOAD'))]
1528 else:
1529 self.conf.argdict['athenaopts'] = trfArgClasses.argSubstepList(["--preloadlib={0}".format(self._envUpdate.value('LD_PRELOAD'))])
1530
1531 # Now update command line with the options we have (including any changes to preload)
1532 if 'athenaopts' in self.conf.argdict:
1533 if currentSubstep is None and "all" in self.conf.argdict['athenaopts'].value:
1534 self._cmd.extend(self.conf.argdict['athenaopts'].value['all'])
1535 elif currentSubstep in self.conf.argdict['athenaopts'].value:
1536 self._cmd.extend(self.conf.argdict['athenaopts'].value[currentSubstep])
1537
1538 if currentSubstep is None:
1539 currentSubstep = 'all'
1540
1541 if self._tryDropAndReload:
1542 if self._isCAEnabled():
1543 msg.info('ignoring "--drop-and-reload" for CA-based transforms, config cleaned up anyway')
1544 elif 'valgrind' in self.conf._argdict and self.conf._argdict['valgrind'].value is True:
1545 msg.info('Disabling "--drop-and-reload" because the job is configured to use Valgrind')
1546 elif 'athenaopts' in self.conf.argdict:
1547 athenaConfigRelatedOpts = ['--config-only','--drop-and-reload']
1548 # Note for athena options we split on '=' so that we properly get the option and not the whole "--option=value" string
1549 if currentSubstep in self.conf.argdict['athenaopts'].value:
1550 conflictOpts = set(athenaConfigRelatedOpts).intersection(set([opt.split('=')[0] for opt in self.conf.argdict['athenaopts'].value[currentSubstep]]))
1551 if len(conflictOpts) > 0:
1552 msg.info('Not appending "--drop-and-reload" to athena command line because these options conflict: {0}'.format(list(conflictOpts)))
1553 else:
1554 msg.info('Appending "--drop-and-reload" to athena options')
1555 self._cmd.append('--drop-and-reload')
1556 else:
1557 msg.info('No Athenaopts for substep {0}, appending "--drop-and-reload" to athena options'.format(self.name))
1558 self._cmd.append('--drop-and-reload')
1559 else:
1560 # This is the 'standard' case - so drop and reload should be ok
1561 msg.info('Appending "--drop-and-reload" to athena options')
1562 self._cmd.append('--drop-and-reload')
1563 else:
1564 msg.info('Skipping test for "--drop-and-reload" in this executor')
1565
1566 if not self._isCAEnabled(): #For CA-jobs, threads and nproc set in runargs file
1567 # For AthenaMT apply --threads=N if threads have been configured via ATHENA_CORE_NUMBER + multithreaded
1568 if self._athenaMT > 0 and not self._disableMT:
1569 if not ('athenaopts' in self.conf.argdict and
1570 any('--threads' in opt for opt in self.conf.argdict['athenaopts'].value[currentSubstep])):
1571 self._cmd.append('--threads=%s' % str(self._athenaMT))
1572
1573 # For AthenaMP apply --nprocs=N if threads have been configured via ATHENA_CORE_NUMBER + multiprocess
1574 if self._athenaMP > 0 and not self._disableMP:
1575 if not ('athenaopts' in self.conf.argdict and
1576 any('--nprocs' in opt for opt in self.conf.argdict['athenaopts'].value[currentSubstep])):
1577 self._cmd.append('--nprocs=%s' % str(self._athenaMP))
1578
1579 # Add topoptions
1580 # Note that _writeAthenaWrapper removes this from the end of _cmd when preparing the options for VTuneCommand, so assumes it comes last.
1581 if self._skeleton or self._skeletonCA:
1582 self._cmd += self._topOptionsFiles
1583 msg.info('Updated script arguments with topoptions: %s', self._cmd)
1584
1585
1586
1588 self,
1589 asetup = None,
1590 dbsetup = None,
1591 ossetup = None
1592 ):
1593 self._originalCmd = self._cmd
1594 self._asetup = asetup
1595 self._dbsetup = dbsetup
1596 self._containerSetup = ossetup
1597 self._workdir = os.getcwd()
1598 self._alreadyInContainer = self._workdir.startswith("/srv")
1599 self._wrapperFile = 'runwrapper.{name}.sh'.format(name = self._name)
1600 self._setupFile = 'setup.{name}.sh'.format(name = self._name)
1601
1602 # Create a setupATLAS script
1603 setupATLAS = 'my_setupATLAS.sh'
1604 with open(setupATLAS, 'w') as f:
1605 print("#!/bin/bash", file=f)
1606 print("""
1607if [ -z $ATLAS_LOCAL_ROOT_BASE ]; then
1608 export ATLAS_LOCAL_ROOT_BASE=/cvmfs/atlas.cern.ch/repo/ATLASLocalRootBase
1609fi
1610source ${ATLAS_LOCAL_ROOT_BASE}/user/atlasLocalSetup.sh"""
1611 , file=f)
1612 os.chmod(setupATLAS, 0o755)
1613
1614 msg.debug(
1615 'Preparing wrapper file {wrapperFileName} with '
1616 'asetup={asetupStatus} and dbsetup={dbsetupStatus}'.format(
1617 wrapperFileName = self._wrapperFile,
1618 asetupStatus = self._asetup,
1619 dbsetupStatus = self._dbsetup
1620 )
1621 )
1622
1623 container_cmd = None
1624 try:
1625 with open(self._wrapperFile, 'w') as wrapper:
1626 print('#!/bin/sh', file=wrapper)
1627 if self._containerSetup is not None:
1628 container_cmd = [ os.path.abspath(setupATLAS),
1629 "-c",
1630 self._containerSetup,
1631 "--pwd",
1632 self._workdir,
1633 "-s",
1634 os.path.join('.', self._setupFile),
1635 "-r"]
1636 print('echo "This wrapper is executed within a container! For a local re-run, do:"', file=wrapper)
1637 print('echo " '+ " ".join(['setupATLAS'] + container_cmd[1:] + [path.join('.', self._wrapperFile)]) + '"', file=wrapper)
1638 print('echo "N.B.: if launching a nested container, navigate to /srv before running the above command"',
1639 file = wrapper)
1640 print('echo " and use --pwd workdir, where workdir is the transform running directory within /srv"',
1641 file=wrapper)
1642 print('echo', file=wrapper)
1643
1644 if asetup:
1645 wfile = wrapper
1646 asetupFile = None
1647 # if the substep is executed within a container, setup athena with a separate script
1648 # e.g. setupATLAS -c el9 -s setup.RDOtoRDOTrigger.sh -r runwrapper.RDOtoRDOTrigger.sh ...
1649 if self._containerSetup is not None:
1650 asetupFile = open(self._setupFile, 'w')
1651 wfile = asetupFile
1652 print(f'source ./{setupATLAS} -q', file=wfile)
1653 print(f'asetup {asetup}', file=wfile)
1654 print('if [ ${?} != "0" ]; then exit 255; fi', file=wfile)
1655 if dbsetup:
1656 dbroot = path.dirname(dbsetup)
1657 dbversion = path.basename(dbroot)
1658 print("# DBRelease setup", file=wrapper)
1659 print('echo Setting up DBRelease {dbroot} environment'.format(dbroot = dbroot), file=wrapper)
1660 print('export DBRELEASE={dbversion}'.format(dbversion = dbversion), file=wrapper)
1661 print('export CORAL_AUTH_PATH={directory}'.format(directory = path.join(dbroot, 'XMLConfig')), file=wrapper)
1662 print('export CORAL_DBLOOKUP_PATH={directory}'.format(directory = path.join(dbroot, 'XMLConfig')), file=wrapper)
1663 print('export TNS_ADMIN={directory}'.format(directory = path.join(dbroot, 'oracle-admin')), file=wrapper)
1664 print('DATAPATH={dbroot}:$DATAPATH'.format(dbroot = dbroot), file=wrapper)
1665 if self._disableMT:
1666 print("# AthenaMT explicitly disabled for this executor", file=wrapper)
1667 if self._disableMP:
1668 print("# AthenaMP explicitly disabled for this executor", file=wrapper)
1669 if self._envUpdate.len > 0:
1670 for envSetting in self._envUpdate.values:
1671 if not envSetting.startswith('LD_PRELOAD'):
1672 print("export", envSetting, file=wrapper)
1673 # If Valgrind is engaged, a serialised Athena configuration file
1674 # is generated for use with a subsequent run of Athena with
1675 # Valgrind.
1676 if 'valgrind' in self.conf._argdict and self.conf._argdict['valgrind'].value is True:
1677 msg.info('Valgrind engaged')
1678 # Define the file name of the serialised Athena
1679 # configuration.
1680 AthenaSerialisedConfigurationFile = "{name}Conf.pkl".format(
1681 name = self._name
1682 )
1683 # Run Athena for generation of its serialised configuration.
1684 print(' '.join(self._cmd), "--config-only={0}".format(AthenaSerialisedConfigurationFile), file=wrapper)
1685 print('if [ $? != "0" ]; then exit 255; fi', file=wrapper)
1686 # Generate a Valgrind command, suppressing or ussing default
1687 # options as requested and extra options as requested.
1688 if 'valgrindDefaultOpts' in self.conf._argdict:
1689 defaultOptions = self.conf._argdict['valgrindDefaultOpts'].value
1690 else:
1691 defaultOptions = True
1692 if 'valgrindExtraOpts' in self.conf._argdict:
1693 extraOptionsList = self.conf._argdict['valgrindExtraOpts'].value
1694 else:
1695 extraOptionsList = None
1696 msg.debug("requested Valgrind command basic options: {options}".format(options = defaultOptions))
1697 msg.debug("requested Valgrind command extra options: {options}".format(options = extraOptionsList))
1698 command = ValgrindCommand(
1699 defaultOptions = defaultOptions,
1700 extraOptionsList = extraOptionsList,
1701 AthenaSerialisedConfigurationFile = \
1702 AthenaSerialisedConfigurationFile
1703 )
1704 msg.debug("Valgrind command: {command}".format(command = command))
1705 print(command, file=wrapper)
1706 # If VTune is engaged, a serialised Athena configuration file
1707 # is generated for use with a subsequent run of Athena with
1708 # VTune.
1709 elif 'vtune' in self.conf._argdict and self.conf._argdict['vtune'].value is True:
1710 msg.info('VTune engaged')
1711 # Define the file name of the serialised Athena
1712 # configuration.
1713 AthenaSerialisedConfigurationFile = "{name}Conf.pkl".format(
1714 name = self._name
1715 )
1716 # Run Athena for generation of its serialised configuration.
1717 print(' '.join(self._cmd), "--config-only={0}".format(AthenaSerialisedConfigurationFile), file=wrapper)
1718 print('if [ $? != "0" ]; then exit 255; fi', file=wrapper)
1719 # Generate a VTune command, suppressing or ussing default
1720 # options as requested and extra options as requested.
1721 if 'vtuneDefaultOpts' in self.conf._argdict:
1722 defaultOptions = self.conf._argdict['vtuneDefaultOpts'].value
1723 else:
1724 defaultOptions = True
1725 if 'vtuneExtraOpts' in self.conf._argdict:
1726 extraOptionsList = self.conf._argdict['vtuneExtraOpts'].value
1727 else:
1728 extraOptionsList = None
1729
1730 # replace the _topOptionsFiles from the Athena command with the AthenaSerialisedConfigurationFile.
1731 if (self._skeleton or self._skeletonCA) and len(self._topOptionsFiles) > 0:
1732 AthenaCommand = self._cmd[:-len(self._topOptionsFiles)]
1733 else:
1734 AthenaCommand = self._cmd
1735 AthenaCommand.append(AthenaSerialisedConfigurationFile)
1736
1737 msg.debug("requested VTune command basic options: {options}".format(options = defaultOptions))
1738 msg.debug("requested VTune command extra options: {options}".format(options = extraOptionsList))
1739 command = VTuneCommand(
1740 defaultOptions = defaultOptions,
1741 extraOptionsList = extraOptionsList,
1742 AthenaCommand = AthenaCommand
1743 )
1744 msg.debug("VTune command: {command}".format(command = command))
1745 print(command, file=wrapper)
1746 else:
1747 msg.info('Valgrind/VTune not engaged')
1748 # run Athena command
1749 print(' '.join(self._cmd), file=wrapper)
1750 os.chmod(self._wrapperFile, 0o755)
1751 except OSError as e:
1752 errMsg = 'error writing athena wrapper {fileName}: {error}'.format(
1753 fileName = self._wrapperFile,
1754 error = e
1755 )
1756 msg.error(errMsg)
1758 trfExit.nameToCode('TRF_EXEC_SETUP_WRAPPER'),
1759 errMsg
1760 )
1761 self._cmd = [ path.join('.', self._wrapperFile) ]
1762 if self._containerSetup is not None:
1763 asetupFile.close()
1764 self._cmd = container_cmd + self._cmd
1765
1766
1767
1769 def _smartMerge(self, fileArg):
1770
1771 if 'selfMerge' not in dir(fileArg):
1772 msg.info('Files in {0} cannot merged (no selfMerge() method is implemented)'.format(fileArg.name))
1773 return
1774
1775 if fileArg.mergeTargetSize == 0:
1776 msg.info('Files in {0} will not be merged as target size is set to 0'.format(fileArg.name))
1777 return
1778
1779
1780 mergeCandidates = [list()]
1781 currentMergeSize = 0
1782 for fname in fileArg.value:
1783 size = fileArg.getSingleMetadata(fname, 'file_size')
1784 if not isinstance(size, int):
1785 msg.warning('File size metadata for {0} was not correct, found type {1}. Aborting merge attempts.'.format(fileArg, type(size)))
1786 return
1787 # if there is no file in the job, then we must add it
1788 if len(mergeCandidates[-1]) == 0:
1789 msg.debug('Adding file {0} to current empty merge list'.format(fname))
1790 mergeCandidates[-1].append(fname)
1791 currentMergeSize += size
1792 continue
1793 # see if adding this file gets us closer to the target size (but always add if target size is negative)
1794 if fileArg.mergeTargetSize < 0 or math.fabs(currentMergeSize + size - fileArg.mergeTargetSize) < math.fabs(currentMergeSize - fileArg.mergeTargetSize):
1795 msg.debug('Adding file {0} to merge list {1} as it gets closer to the target size'.format(fname, mergeCandidates[-1]))
1796 mergeCandidates[-1].append(fname)
1797 currentMergeSize += size
1798 continue
1799 # close this merge list and start a new one
1800 msg.debug('Starting a new merge list with file {0}'.format(fname))
1801 mergeCandidates.append([fname])
1802 currentMergeSize = size
1803
1804 msg.debug('First pass splitting will merge files in this way: {0}'.format(mergeCandidates))
1805
1806 if len(mergeCandidates) == 1:
1807 # Merging to a single file, so use the original filename that the transform
1808 # was started with
1809 mergeNames = [fileArg.originalName]
1810 else:
1811 # Multiple merge targets, so we need a set of unique names
1812 counter = 0
1813 mergeNames = []
1814 for mergeGroup in mergeCandidates:
1815 # Note that the individual worker files get numbered with 3 digit padding,
1816 # so these non-padded merges should be fine
1817 mergeName = fileArg.originalName + '_{0}'.format(counter)
1818 while path.exists(mergeName):
1819 counter += 1
1820 mergeName = fileArg.originalName + '_{0}'.format(counter)
1821 mergeNames.append(mergeName)
1822 counter += 1
1823 # Now actually do the merges
1824 for targetName, mergeGroup, counter in zip(mergeNames, mergeCandidates, list(range(len(mergeNames)))):
1825 msg.info('Want to merge files {0} to {1}'.format(mergeGroup, targetName))
1826 if len(mergeGroup) <= 1:
1827 msg.info('Skip merging for single file')
1828 else:
1829
1830 self._myMerger.append(fileArg.selfMerge(output=targetName, inputs=mergeGroup, counter=counter, argdict=self.conf.argdict))
1831
1832
1834 #tgzipping JiveXML files
1835 targetTGZName = self.conf.dataDictionary['TXT_JIVEXMLTGZ'].value[0]
1836 if os.path.exists(targetTGZName):
1837 os.remove(targetTGZName)
1838
1839 import tarfile
1840 fNameRE = re.compile(r"JiveXML\_\d+\_\d+.xml")
1841
1842 # force gz compression
1843 tar = tarfile.open(targetTGZName, "w:gz")
1844 for fName in os.listdir('.'):
1845 matches = fNameRE.findall(fName)
1846 if len(matches) > 0:
1847 if fNameRE.findall(fName)[0] == fName:
1848 msg.info('adding %s to %s', fName, targetTGZName)
1849 tar.add(fName)
1850
1851 tar.close()
1852 msg.info('JiveXML compression: %s has been written and closed.', targetTGZName)
1853
1854
1855
1857
1858 # Here we validate, but will suppress any errors
1859 def validate(self):
1860 self.setValStart()
1861 try:
1862 super(optionalAthenaExecutor, self).validate()
1864 # In this case we hold this exception until the logfile has been scanned
1865 msg.warning('Validation failed for {0}: {1}'.format(self._name, e))
1866 self._isValidated = False
1867 self._errMsg = e.errMsg
1868 self._rc = e.errCode
1869 self._valStop = os.times()
1870 msg.debug('valStop time is {0}'.format(self._valStop))
1871
1872
1874
1885 def __init__(self, name = 'hybridPOOLMerge', trf = None, conf = None, skeletonFile=None, skeletonCA='RecJobTransforms.MergePool_Skeleton',
1886 inData = set(), outData = set(), exe = 'athena.py', exeArgs = ['athenaopts'], substep = None, inputEventTest = True,
1887 perfMonFile = None, tryDropAndReload = True, extraRunargs = {},
1888 manualDataDictionary = None, memMonitor = True):
1889
1890 super(POOLMergeExecutor, self).__init__(name, trf=trf, conf=conf, skeletonFile=skeletonFile, skeletonCA=skeletonCA,
1891 inData=inData, outData=outData, exe=exe, exeArgs=exeArgs, substep=substep,
1892 inputEventTest=inputEventTest, perfMonFile=perfMonFile,
1893 tryDropAndReload=tryDropAndReload, extraRunargs=extraRunargs,
1894 manualDataDictionary=manualDataDictionary, memMonitor=memMonitor)
1895
1896 def preExecute(self, input = set(), output = set()):
1897 self.setPreExeStart()
1898 super(POOLMergeExecutor, self).preExecute(input=input, output=output)
1899
1900
1901 def execute(self):
1902 # First call the parent executor, which will manage the athena execution for us
1903 super(POOLMergeExecutor, self).execute()
1904
1905
1906
1909
1911 def preExecute(self, input=set(), output=set()):
1912 self.setPreExeStart()
1913 msg.debug('Preparing for execution of {0} with inputs {1} and outputs {2}'.format(self.name, input, output))
1914 if 'NTUP_PILEUP' not in output:
1915 # New derivation framework transform uses "formats"
1916 if 'formats' not in self.conf.argdict:
1917 raise trfExceptions.TransformExecutionException(trfExit.nameToCode('TRF_REDUCTION_CONFIG_ERROR'),
1918 'No derivation configuration specified')
1919
1920 if ('DAOD' not in output) and ('D2AOD' not in output):
1921 raise trfExceptions.TransformExecutionException(trfExit.nameToCode('TRF_REDUCTION_CONFIG_ERROR'),
1922 'No base name for DAOD output')
1923
1924 formatList = []
1925 if 'formats' in self.conf.argdict: formatList = self.conf.argdict['formats'].value
1926 for reduction in formatList:
1927 if ('DAOD' in output):
1928 dataType = 'DAOD_' + reduction
1929 if 'augmentations' not in self.conf.argdict:
1930 outputName = 'DAOD_' + reduction + '.' + self.conf.argdict['outputDAODFile'].value[0]
1931 else:
1932 for val in self.conf.argdict['augmentations'].value:
1933 if reduction in val.split(':')[0]:
1934 outputName = 'DAOD_' + val.split(':')[1] + '.' + self.conf.argdict['outputDAODFile'].value[0]
1935 break
1936 else:
1937 outputName = 'DAOD_' + reduction + '.' + self.conf.argdict['outputDAODFile'].value[0]
1938
1939 if ('D2AOD' in output):
1940 dataType = 'D2AOD_' + reduction
1941 outputName = 'D2AOD_' + reduction + '.' + self.conf.argdict['outputD2AODFile'].value[0]
1942
1943 msg.info('Adding reduction output type {0}'.format(dataType))
1944 output.add(dataType)
1945 newReduction = trfArgClasses.argPOOLFile(outputName, io='output', runarg=True, type='AOD',
1946 name=reduction)
1947 # References to _trf - can this be removed?
1948 self.conf.dataDictionary[dataType] = newReduction
1949
1950 # Clean up the stub file from the executor input and the transform's data dictionary
1951 # (we don't remove the actual argFile instance)
1952 if ('DAOD' in output):
1953 output.remove('DAOD')
1954 del self.conf.dataDictionary['DAOD']
1955 del self.conf.argdict['outputDAODFile']
1956 if ('D2AOD' in output):
1957 output.remove('D2AOD')
1958 del self.conf.dataDictionary['D2AOD']
1959 del self.conf.argdict['outputD2AODFile']
1960
1961 msg.info('Data dictionary is now: {0}'.format(self.conf.dataDictionary))
1962 msg.info('Input/Output: {0}/{1}'.format(input, output))
1963
1964 msg.info('Data dictionary is now: {0}'.format(self.conf.dataDictionary))
1965 msg.info('Input/Output: {0}/{1}'.format(input, output))
1966 super(reductionFrameworkExecutor, self).preExecute(input, output)
1967
1968
1969
1971 def __init__(self, name='DQHistMerge', trf=None, conf=None, inData=set(['HIST_AOD', 'HIST_ESD']), outData=set(['HIST']),
1972 exe='DQHistogramMerge.py', exeArgs = [], memMonitor = True):
1973
1974 self._histMergeList = 'HISTMergeList.txt'
1975
1976 super(DQMergeExecutor, self).__init__(name=name, trf=trf, conf=conf, inData=inData, outData=outData, exe=exe,
1977 exeArgs=exeArgs, memMonitor=memMonitor)
1978
1979
1980 def preExecute(self, input = set(), output = set()):
1981 self.setPreExeStart()
1982 msg.debug('Preparing for execution of {0} with inputs {1} and outputs {2}'.format(self.name, input, output))
1983
1984 super(DQMergeExecutor, self).preExecute(input=input, output=output)
1985
1986 # Write the list of files to be merged
1987 with open(self._histMergeList, 'w') as DQMergeFile:
1988 for dataType in input:
1989 for fname in self.conf.dataDictionary[dataType].value:
1990 self.conf.dataDictionary[dataType]._getNumberOfEvents([fname])
1991 print(fname, file=DQMergeFile)
1992
1993 self._cmd.append(self._histMergeList)
1994
1995 # Add the output file
1996 if len(output) != 1:
1997 raise trfExceptions.TransformExecutionException(trfExit.nameToCode('TRF_EXEC_SETUP_FAIL'),
1998 'One (and only one) output file must be given to {0} (got {1})'.format(self.name, len(output)))
1999 outDataType = list(output)[0]
2000 self._cmd.append(self.conf.dataDictionary[outDataType].value[0])
2001
2002 # Set the run_post_processing to True/False
2003 if (self.conf._argdict.get("run_post_processing",False)):
2004 self._cmd.append('True')
2005 else:
2006 self._cmd.append('False')
2007
2008 if (self.conf._argdict.get("is_incremental_merge",False)):
2009 self._cmd.append('True')
2010 else:
2011 self._cmd.append('False')
2012
2013 for k in ("excludeHist","excludeDir"):
2014 if k in self.conf._argdict:
2015 self._cmd.append("--{0}={1}".format(k,self.conf._argdict[k]))
2016
2017
2018 def validate(self):
2019 self.setValStart()
2020 super(DQMergeExecutor, self).validate()
2021
2022 exitErrorMessage = ''
2023 # Base class validation successful, Now scan the logfile for missed errors.
2024 try:
2026 worstError = logScan.worstError()
2027
2028 # In general we add the error message to the exit message, but if it's too long then don't do
2029 # that and just say look in the jobReport
2030 if worstError['firstError']:
2031 if len(worstError['firstError']['message']) > logScan._msgLimit:
2032 exitErrorMessage = "Long {0} message at line {1}" \
2033 " (see jobReport for further details)".format(worstError['level'],
2034 worstError['firstError']['firstLine'])
2035 else:
2036 exitErrorMessage = "Logfile error in {0}: \"{1}\"".format(self._logFileName,
2037 worstError['firstError']['message'])
2038 except OSError as e:
2039 exitCode = trfExit.nameToCode('TRF_EXEC_LOGERROR')
2041 'Exception raised while attempting to scan logfile {0}: {1}'.format(self._logFileName, e))
2042
2043 if worstError['nLevel'] == stdLogLevels['ERROR'] and (
2044 'ignoreErrors' in self.conf.argdict and self.conf.argdict['ignoreErrors'].value is True):
2045 msg.warning('Found ERRORs in the logfile, but ignoring this as ignoreErrors=True (see jobReport for details)')
2046
2047 elif worstError['nLevel'] >= stdLogLevels['ERROR']:
2048 self._isValidated = False
2049 msg.error('Fatal error in script logfile (level {0})'.format(worstError['level']))
2050 exitCode = trfExit.nameToCode('TRF_EXEC_LOGERROR')
2051 raise trfExceptions.TransformLogfileErrorException(exitCode, 'Fatal error in script logfile: "{0}"'.format(exitErrorMessage))
2052
2053 # Must be ok if we got here!
2054 msg.info('Executor {0} has validated successfully'.format(self.name))
2055 self._isValidated = True
2056
2057 self._valStop = os.times()
2058 msg.debug('valStop time is {0}'.format(self._valStop))
2059
2060
2062 def __init__(self, name='DQMPostProcess', trf=None, conf=None, inData=set(['HIST']), outData=set(['HIST']),
2063 exe='DQM_Tier0Wrapper_tf.py', exeArgs = [], memMonitor = True):
2064
2065 self._histMergeList = 'HISTMergeList.txt'
2066
2067 super(DQMPostProcessExecutor, self).__init__(name=name, trf=trf, conf=conf, inData=inData, outData=outData, exe=exe,
2068 exeArgs=exeArgs, memMonitor=memMonitor)
2069
2070
2071 def preExecute(self, input = set(), output = set()):
2072 self.setPreExeStart()
2073 msg.debug('Preparing for execution of {0} with inputs {1} and outputs {2}'.format(self.name, input, output))
2074
2075 super(DQMPostProcessExecutor, self).preExecute(input=input, output=output)
2076
2077 #build input file list (typically only one):
2078 dsName=self.conf.argdict["inputHISTFile"].dataset
2079 inputList=[]
2080 for dataType in input:
2081 for fname in self.conf.dataDictionary[dataType].value:
2082 #if no dataset name is give, guess it from file name
2083 if not dsName: dsName=".".join(fname.split('.')[0:4])
2084 inputList.append("#".join([dsName,fname]))
2085
2086
2087 if len(output) != 1:
2088 raise trfExceptions.TransformExecutionException(trfExit.nameToCode('TRF_EXEC_SETUP_FAIL'),
2089 'One (and only one) output file must be given to {0} (got {1})'.format(self.name, len(output)))
2090 outDataType = list(output)[0]
2091 #build argument json:
2092 #T0 uses keys: 'allowCOOLUpload', 'doWebDisplay', 'incrementalMode', 'inputHistFiles', 'mergeParams', 'outputHistFile', 'postProcessing', 'reverseFileOrder', 'servers'
2093 #more keys: runNumber, streamName,projectTag,filepaths,productionMode,skipMerge
2094 wrapperParams={"inputHistFiles" : inputList,
2095 "outputHistFile" : dsName+"#"+self.conf.dataDictionary[outDataType].value[0],
2096 "incrementalMode": "True" if self.conf._argdict.get("is_incremental_merge",False) else "False",
2097 "postProcessing" : "True" if self.conf._argdict.get("run_post_processing",False) else "False",
2098 "doWebDisplay" : "True" if self.conf._argdict.get("doWebDisplay",False) else "False",
2099 "allowCOOLUpload": "True" if self.conf._argdict.get("allowCOOLUpload",False) else "False",
2100 "mergeParams" : "",
2101
2102 }
2103 if "servers" in self.conf._argdict:
2104 wrapperParams["server"]=self.conf._argdict["servers"]
2105
2106 for k in ("excludeHist","excludeDir"):
2107 if k in self.conf._argdict:
2108 wrapperParams["mergeParams"]+=(" --{0}={1}".format(k,self.conf._argdict[k]))
2109
2110
2111 with open("args.json", "w") as f:
2112 json.dump(wrapperParams, f)
2113
2114 self._cmd.append("--argJSON=args.json")
2115
2116
2117
2118 def validate(self):
2119 self.setValStart()
2120 super(DQMPostProcessExecutor, self).validate()
2121
2122 exitErrorMessage = ''
2123 # Base class validation successful, Now scan the logfile for missed errors.
2124 try:
2126 worstError = logScan.worstError()
2127
2128 # In general we add the error message to the exit message, but if it's too long then don't do
2129 # that and just say look in the jobReport
2130 if worstError['firstError']:
2131 if len(worstError['firstError']['message']) > logScan._msgLimit:
2132 exitErrorMessage = "Long {0} message at line {1}" \
2133 " (see jobReport for further details)".format(worstError['level'],
2134 worstError['firstError']['firstLine'])
2135 else:
2136 exitErrorMessage = "Logfile error in {0}: \"{1}\"".format(self._logFileName,
2137 worstError['firstError']['message'])
2138 except OSError as e:
2139 exitCode = trfExit.nameToCode('TRF_EXEC_LOGERROR')
2141 'Exception raised while attempting to scan logfile {0}: {1}'.format(self._logFileName, e))
2142
2143 if worstError['nLevel'] == stdLogLevels['ERROR'] and (
2144 'ignoreErrors' in self.conf.argdict and self.conf.argdict['ignoreErrors'].value is True):
2145 msg.warning('Found ERRORs in the logfile, but ignoring this as ignoreErrors=True (see jobReport for details)')
2146
2147 elif worstError['nLevel'] >= stdLogLevels['ERROR']:
2148 self._isValidated = False
2149 msg.error('Fatal error in script logfile (level {0})'.format(worstError['level']))
2150 exitCode = trfExit.nameToCode('TRF_EXEC_LOGERROR')
2151 raise trfExceptions.TransformLogfileErrorException(exitCode, 'Fatal error in script logfile: "{0}"'.format(exitErrorMessage))
2152
2153 # Must be ok if we got here!
2154 msg.info('Executor {0} has validated successfully'.format(self.name))
2155 self._isValidated = True
2156
2157 self._valStop = os.times()
2158 msg.debug('valStop time is {0}'.format(self._valStop))
2159
2161
2162 def preExecute(self, input = set(), output = set()):
2163 self.setPreExeStart()
2164 msg.debug('[NTUP] Preparing for execution of {0} with inputs {1} and outputs {2}'.format(self.name, input, output))
2165
2166 # Basic command, and allow overwrite of the output file
2167 if self._exe is None:
2168 self._exe = 'hadd'
2169 self._cmd = [self._exe, "-f"]
2170
2171
2172 # Add the output file
2173 if len(output) != 1:
2174 raise trfExceptions.TransformExecutionException(trfExit.nameToCode('TRF_EXEC_SETUP_FAIL'),
2175 'One (and only one) output file must be given to {0} (got {1})'.format(self.name, len(output)))
2176 outDataType = list(output)[0]
2177 self._cmd.append(self.conf.dataDictionary[outDataType].value[0])
2178 # Add to be merged to the cmd chain
2179 for dataType in input:
2180 self._cmd.extend(self.conf.dataDictionary[dataType].value)
2181
2182 super(NTUPMergeExecutor, self).preExecute(input=input, output=output)
2183
2184
2186 """Executor for running physvalPostProcessing.py with <input> <output> args"""
2187
2188 def preExecute(self, input = set(), output = set()):
2189 self.setPreExeStart()
2190 msg.debug('[NTUP] Preparing for execution of {0} with inputs {1} and outputs {2}'.format(self.name, input, output))
2191
2192 self._cmd = [self.exe, ]
2193
2194 if len(input) != 1 or len(output) != 1:
2195 raise trfExceptions.TransformExecutionException(trfExit.nameToCode('TRF_EXEC_SETUP_FAIL'),
2196 f'Exactly one input and one output must be specified (got inputs={len(input)}, outputs={len(output)})')
2197
2198 self._cmd.append(self.conf.dataDictionary[list(input)[0]].value[0])
2199 self._cmd.append(self.conf.dataDictionary[list(output)[0]].value[0])
2200
2201 # Finalize execution setup by calling the parent class method
2202 super(NtupPhysValPostProcessingExecutor, self).preExecute(input=input, output=output)
2203
2204
2206
2207 def preExecute(self, input = set(), output = set()):
2208 self.setPreExeStart()
2209 self._inputBS = list(input)[0]
2210 self._outputBS = list(output)[0]
2212 self._useStubFile = False
2213 if 'maskEmptyInputs' in self.conf.argdict and self.conf.argdict['maskEmptyInputs'].value is True:
2214 eventfullFiles = []
2215 for fname in self.conf.dataDictionary[self._inputBS].value:
2216 nEvents = self.conf.dataDictionary[self._inputBS].getSingleMetadata(fname, 'nentries')
2217 msg.debug('Found {0} events in file {1}'.format(nEvents, fname))
2218 if isinstance(nEvents, int) and nEvents > 0:
2219 eventfullFiles.append(fname)
2220 self._maskedFiles = list(set(self.conf.dataDictionary[self._inputBS].value) - set(eventfullFiles))
2221 if len(self._maskedFiles) > 0:
2222 msg.info('The following input files are masked because they have 0 events: {0}'.format(' '.join(self._maskedFiles)))
2223 if len(eventfullFiles) == 0:
2224 if 'emptyStubFile' in self.conf.argdict and path.exists(self.conf.argdict['emptyStubFile'].value):
2225 self._useStubFile = True
2226 msg.info("All input files are empty - will use stub file {0} as output".format(self.conf.argdict['emptyStubFile'].value))
2227 else:
2228 raise trfExceptions.TransformExecutionException(trfExit.nameToCode('TRF_INPUT_FILE_ERROR'),
2229 'All input files had zero events - aborting BS merge')
2230
2231 # Write the list of input files to a text file, so that testMergedFiles can swallow it
2232 self._mergeBSFileList = '{0}.list'.format(self._name)
2233 self._mergeBSLogfile = '{0}.out'.format(self._name)
2234 try:
2235 with open(self._mergeBSFileList, 'w') as BSFileList:
2236 for fname in self.conf.dataDictionary[self._inputBS].value:
2237 if fname not in self._maskedFiles:
2238 print(fname, file=BSFileList)
2239 except OSError as e:
2240 errMsg = 'Got an error when writing list of BS files to {0}: {1}'.format(self._mergeBSFileList, e)
2241 msg.error(errMsg)
2242 raise trfExceptions.TransformExecutionException(trfExit.nameToCode('TRF_EXEC_SETUP_WRAPPER'), errMsg)
2243
2244 # Hope that we were given a correct filename...
2245 self._outputFilename = self.conf.dataDictionary[self._outputBS].value[0]
2246 if self._outputFilename.endswith('._0001.data'):
2247 self._doRename = False
2248 self._outputFilename = self._outputFilename.split('._0001.data')[0]
2249 elif self.conf.argdict['allowRename'].value is True:
2250 # OK, non-fatal, we go for a renaming
2251 msg.info('Output filename does not end in "._0001.data" will proceed, but be aware that the internal filename metadata will be wrong')
2252 self._doRename = True
2253 else:
2254 # No rename allowed, so we are dead...
2255 errmsg = 'Output filename for outputBS_MRGFile must end in "._0001.data" or infile metadata will be wrong'
2256 raise trfExceptions.TransformExecutionException(trfExit.nameToCode('TRF_OUTPUT_FILE_ERROR'), errmsg)
2257
2258 # Set the correct command for execution
2259 self._cmd = [self._exe, self._mergeBSFileList, '0', self._outputFilename]
2260
2261 super(bsMergeExecutor, self).preExecute(input=input, output=output)
2262
2263 def execute(self):
2264 if self._useStubFile:
2265 # Need to fake execution!
2266 self._exeStart = os.times()
2267 msg.debug('exeStart time is {0}'.format(self._exeStart))
2268 msg.info("Using stub file for empty BS output - execution is fake")
2269 if self._outputFilename != self.conf.argdict['emptyStubFile'].value:
2270 os.rename(self.conf.argdict['emptyStubFile'].value, self._outputFilename)
2271 self._memMonitor = False
2272 self._hasExecuted = True
2273 self._rc = 0
2274 self._exeStop = os.times()
2275 msg.debug('exeStop time is {0}'.format(self._exeStop))
2276 else:
2277 super(bsMergeExecutor, self).execute()
2278
2279 def postExecute(self):
2280 if self._useStubFile:
2281 pass
2282 elif self._doRename:
2283 self._expectedOutput = self._outputFilename + '._0001.data'
2284 msg.info('Renaming {0} to {1}'.format(self._expectedOutput, self.conf.dataDictionary[self._outputBS].value[0]))
2285 try:
2286 os.rename(self._outputFilename + '._0001.data', self.conf.dataDictionary[self._outputBS].value[0])
2287 except OSError as e:
2288 raise trfExceptions.TransformExecutionException(trfExit.nameToCode('TRF_OUTPUT_FILE_ERROR'),
2289 'Exception raised when renaming {0} to {1}: {2}'.format(self._outputFilename, self.conf.dataDictionary[self._outputBS].value[0], e))
2290 super(bsMergeExecutor, self).postExecute()
2291
2292
2293
2295
2296 def preExecute(self, input = set(), output = set()):
2297 self.setPreExeStart()
2298 self._memMonitor = False
2299
2300 #archiving
2301 if self._exe == 'zip':
2302 if 'outputArchFile' not in self.conf.argdict:
2303 raise trfExceptions.TransformExecutionException(trfExit.nameToCode('TRF_ARG_MISSING'), 'Missing output file name')
2304
2305 self._cmd = ['python']
2306 try:
2307 with open('zip_wrapper.py', 'w') as zip_wrapper:
2308 print("import zipfile, os, shutil", file=zip_wrapper)
2309 if os.path.exists(self.conf.argdict['outputArchFile'].value[0]):
2310 #appending input file(s) to existing archive
2311 print("zf = zipfile.ZipFile('{}', mode='a', allowZip64=True)".format(self.conf.argdict['outputArchFile'].value[0]), file=zip_wrapper)
2312 else:
2313 #creating new archive
2314 print("zf = zipfile.ZipFile('{}', mode='w', allowZip64=True)".format(self.conf.argdict['outputArchFile'].value[0]), file=zip_wrapper)
2315 print("for f in {}:".format(self.conf.argdict['inputDataFile'].value), file=zip_wrapper)
2316 #This module gives false positives (as of python 3.7.0). Will also check the name for ".zip"
2317 #print >> zip_wrapper, " if zipfile.is_zipfile(f):"
2318 print(" if zipfile.is_zipfile(f) and '.zip' in f:", file=zip_wrapper)
2319 print(" archive = zipfile.ZipFile(f, mode='r')", file=zip_wrapper)
2320 print(" print 'Extracting input zip file {0} to temporary directory {1}'.format(f,'tmp')", file=zip_wrapper)
2321 print(" archive.extractall('tmp')", file=zip_wrapper)
2322 print(" archive.close()", file=zip_wrapper)
2323 # remove stuff as soon as it is saved to output in order to save disk space at worker node
2324 print(" if os.access(f, os.F_OK):", file=zip_wrapper)
2325 print(" print 'Removing input zip file {}'.format(f)", file=zip_wrapper)
2326 print(" os.unlink(f)", file=zip_wrapper)
2327 print(" if os.path.isdir('tmp'):", file=zip_wrapper)
2328 print(" for root, dirs, files in os.walk('tmp'):", file=zip_wrapper)
2329 print(" for name in files:", file=zip_wrapper)
2330 print(" print 'Zipping {}'.format(name)", file=zip_wrapper)
2331 print(" zf.write(os.path.join(root, name), name, compress_type=zipfile.ZIP_STORED)", file=zip_wrapper)
2332 print(" shutil.rmtree('tmp')", file=zip_wrapper)
2333 print(" else:", file=zip_wrapper)
2334 print(" print 'Zipping {}'.format(os.path.basename(f))", file=zip_wrapper)
2335 print(" zf.write(f, arcname=os.path.basename(f), compress_type=zipfile.ZIP_STORED)", file=zip_wrapper)
2336 print(" if os.access(f, os.F_OK):", file=zip_wrapper)
2337 print(" print 'Removing input file {}'.format(f)", file=zip_wrapper)
2338 print(" os.unlink(f)", file=zip_wrapper)
2339 print("zf.close()", file=zip_wrapper)
2340 os.chmod('zip_wrapper.py', 0o755)
2341 except OSError as e:
2342 errMsg = 'error writing zip wrapper {fileName}: {error}'.format(fileName = 'zip_wrapper.py',
2343 error = e
2344 )
2345 msg.error(errMsg)
2346 raise trfExceptions.TransformExecutionException(trfExit.nameToCode('TRF_EXEC_SETUP_WRAPPER'),
2347 errMsg
2348 )
2349 self._cmd.append('zip_wrapper.py')
2350
2351 #unarchiving
2352 elif self._exe == 'unarchive':
2353 import zipfile
2354 for infile in self.conf.argdict['inputArchFile'].value:
2355 if not zipfile.is_zipfile(infile):
2356 raise trfExceptions.TransformExecutionException(trfExit.nameToCode('TRF_INPUT_FILE_ERROR'),
2357 'An input file is not a zip archive - aborting unpacking')
2358 self._cmd = ['python']
2359 try:
2360 with open('unarchive_wrapper.py', 'w') as unarchive_wrapper:
2361 print("import zipfile", file=unarchive_wrapper)
2362 print("for f in {}:".format(self.conf.argdict['inputArchFile'].value), file=unarchive_wrapper)
2363 print(" archive = zipfile.ZipFile(f, mode='r')", file=unarchive_wrapper)
2364 print(" path = '{}'".format(self.conf.argdict['path']), file=unarchive_wrapper)
2365 print(" print 'Extracting archive {0} to {1}'.format(f,path)", file=unarchive_wrapper)
2366 print(" archive.extractall(path)", file=unarchive_wrapper)
2367 print(" archive.close()", file=unarchive_wrapper)
2368 os.chmod('unarchive_wrapper.py', 0o755)
2369 except OSError as e:
2370 errMsg = 'error writing unarchive wrapper {fileName}: {error}'.format(fileName = 'unarchive_wrapper.py',
2371 error = e
2372 )
2373 msg.error(errMsg)
2374 raise trfExceptions.TransformExecutionException(trfExit.nameToCode('TRF_EXEC_SETUP_WRAPPER'),
2375 errMsg
2376 )
2377 self._cmd.append('unarchive_wrapper.py')
2378 super(archiveExecutor, self).preExecute(input=input, output=output)
static Double_t rc
void print(char *figname, TCanvas *c1)
#define min(a, b)
Definition cfImp.cxx:40
Argument class for substep lists, suitable for preExec/postExec.
Class holding the update to an environment that will be passed on to an executor.
Definition trfEnv.py:15
Base class for execution exceptions.
Exception class for validation failures detected by parsing logfiles.
Specialist execution class for DQM post-processing of histograms.
Definition trfExe.py:2061
__init__(self, name='DQMPostProcess', trf=None, conf=None, inData=set(['HIST']), outData=set(['HIST']), exe='DQM_Tier0Wrapper_tf.py', exeArgs=[], memMonitor=True)
Definition trfExe.py:2063
preExecute(self, input=set(), output=set())
Definition trfExe.py:2071
Specialist execution class for merging DQ histograms.
Definition trfExe.py:1970
__init__(self, name='DQHistMerge', trf=None, conf=None, inData=set(['HIST_AOD', 'HIST_ESD']), outData=set(['HIST']), exe='DQHistogramMerge.py', exeArgs=[], memMonitor=True)
Definition trfExe.py:1972
preExecute(self, input=set(), output=set())
Definition trfExe.py:1980
Specialist execution class for merging NTUPLE files.
Definition trfExe.py:2160
preExecute(self, input=set(), output=set())
Definition trfExe.py:2162
Specialist execution class for running post processing on merged PHYVAL NTUPLE file.
Definition trfExe.py:2185
preExecute(self, input=set(), output=set())
Definition trfExe.py:2188
preExecute(self, input=set(), output=set())
Definition trfExe.py:1896
__init__(self, name='hybridPOOLMerge', trf=None, conf=None, skeletonFile=None, skeletonCA='RecJobTransforms.MergePool_Skeleton', inData=set(), outData=set(), exe='athena.py', exeArgs=['athenaopts'], substep=None, inputEventTest=True, perfMonFile=None, tryDropAndReload=True, extraRunargs={}, manualDataDictionary=None, memMonitor=True)
Initialise hybrid POOL merger athena executor.
Definition trfExe.py:1888
Archive transform.
Definition trfExe.py:2294
preExecute(self, input=set(), output=set())
Definition trfExe.py:2296
_writeAthenaWrapper(self, asetup=None, dbsetup=None, ossetup=None)
Write a wrapper script which runs asetup and then Athena.
Definition trfExe.py:1592
_smartMerge(self, fileArg)
Manage smart merging of output files.
Definition trfExe.py:1769
preExecute(self, input=set(), output=set())
Definition trfExe.py:979
__init__(self, name='athena', trf=None, conf=None, skeletonFile=None, skeletonCA=None, inData=set(), outData=set(), inputDataTypeCountCheck=None, exe='athena.py', exeArgs=['athenaopts'], substep=None, inputEventTest=True, perfMonFile=None, tryDropAndReload=True, extraRunargs={}, runtimeRunargs={}, literalRunargs=[], dataArgs=[], checkEventCount=False, errorMaskFiles=None, manualDataDictionary=None, memMonitor=True, disableMT=False, disableMP=False, onlyMP=False, onlyMT=False, onlyMPWithRunargs=None)
Initialise athena executor.
Definition trfExe.py:892
_prepAthenaCommandLine(self)
Prepare the correct command line to be used to invoke athena.
Definition trfExe.py:1475
_isCAEnabled(self)
Check if running with CA.
Definition trfExe.py:1447
_skeletonCA
Handle MPI setup.
Definition trfExe.py:908
_tryDropAndReload
Add –drop-and-reload if possible (and allowed!).
Definition trfExe.py:896
_envUpdate
Look for environment updates and perpare the athena command line.
Definition trfExe.py:1239
Specalise the script executor to deal with the BS merge oddity of excluding empty DRAWs.
Definition trfExe.py:2205
preExecute(self, input=set(), output=set())
Definition trfExe.py:2207
__init__(self, name='Dummy', trf=None, conf=None, inData=set(), outData=set())
Definition trfExe.py:596
__init__(self, name='Echo', trf=None)
Definition trfExe.py:574
setFromTransform(self, trf)
Set configuration properties from the parent transform.
Definition trfExe.py:113
addToArgdict(self, key, value)
Add a new object to the argdict.
Definition trfExe.py:118
addToDataDictionary(self, key, value)
Add a new object to the dataDictionary.
Definition trfExe.py:122
__init__(self, argdict={}, dataDictionary={}, firstExecutor=False)
Configuration for an executor.
Definition trfExe.py:63
Special executor that will enable a logfile scan as part of its validation.
Definition trfExe.py:506
__init__(self, name='Logscan')
Definition trfExe.py:507
preExecute(self, input=set(), output=set())
Definition trfExe.py:512
Athena executor where failure is not consisered fatal.
Definition trfExe.py:1856
Specialist executor to manage the handling of multiple implicit input and output files within the der...
Definition trfExe.py:1908
preExecute(self, input=set(), output=set())
Take inputDAODFile and setup the actual outputs needed in this job.
Definition trfExe.py:1911
preExecute(self, input=set(), output=set())
Definition trfExe.py:658
__init__(self, name='Script', trf=None, conf=None, inData=set(), outData=set(), exe=None, exeArgs=None, memMonitor=True)
Definition trfExe.py:621
Executors always only even execute a single step, as seen by the transform.
Definition trfExe.py:127
myMerger(self)
Now define properties for these data members.
Definition trfExe.py:207
__init__(self, name='Dummy', trf=None, conf=None, inData=set(), outData=set())
Base class initaliser for transform executors.
Definition trfExe.py:138
conf
Executor configuration:
Definition trfExe.py:163
preExecute(self, input=set(), output=set())
Definition trfExe.py:471
doAll(self, input=set(), output=set())
Convenience function.
Definition trfExe.py:499
Logfile suitable for scanning logfiles with an athena flavour, i.e., lines of the form "SERVICE LOGL...
Small class used for vailiadating event counts between input and output files.
Class of patterns that can be ignored from athena logfiles.
STL class.
std::vector< std::string > intersection(std::vector< std::string > &v1, std::vector< std::string > &v2)
bool add(const std::string &hname, TKey *tobj)
Definition fastadd.cxx:55
std::vector< std::string > split(const std::string &s, const std::string &t=":")
Definition hcg.cxx:179
Transform argument class definitions.
Module for transform exit codes.
Contains functions related Athena Job Options files.
Logging configuration for ATLAS job transforms.
Utilities for handling MPI-Athena jobs.
Utilities for handling AthenaMP jobs.
Utilities for handling AthenaMT jobs.
Transform utility functions.
Validation control for job transforms.
Definition index.py:1
_encoding_stream(s)
Definition trfExe.py:45