ATLAS Offline Software
Loading...
Searching...
No Matches
python.EventLoopCPRunScript.EventLoopCPRunScript Class Reference
Inheritance diagram for python.EventLoopCPRunScript.EventLoopCPRunScript:
Collaboration diagram for python.EventLoopCPRunScript.EventLoopCPRunScript:

Public Member Functions

 __init__ (self)
 addCustomArguments (self)
 makeAlgSequence (self)
 readSamples (self)
 moveOutputFiles (self)
 submitDir (self)
 getExitCode (self)
 configureAndSubmit (self)
 runInChildProcess (self)
 run (self)

Public Attributes

 algSeq = None
list outputStreams = []
 sampleHandler = ROOT.SH.SampleHandler()
 inputList
 job = ROOT.EL.Job()
 submitDir

Protected Member Functions

 _dumpAlgSequence (self)
 _combineFullConfig (self)

Static Protected Member Functions

 _flushOutput ()

Static Protected Attributes

str _algSequenceFile = "_alg_sequence.json"

Detailed Description

Definition at line 6 of file EventLoopCPRunScript.py.

Constructor & Destructor Documentation

◆ __init__()

python.EventLoopCPRunScript.EventLoopCPRunScript.__init__ ( self)

Definition at line 7 of file EventLoopCPRunScript.py.

7 def __init__(self):
8 super().__init__()
9 self.logger.info("EventLoopCPRunScript initialized")
10 self.addCustomArguments()
11 self.algSeq = None
12 self.outputStreams = []
13 # Avoid putting call to parse_args() here! Otherwise it is hard to retrieve the parser infos
14

Member Function Documentation

◆ _combineFullConfig()

python.EventLoopCPRunScript.EventLoopCPRunScript._combineFullConfig ( self)
protected

Definition at line 81 of file EventLoopCPRunScript.py.

81 def _combineFullConfig(self):
82 from AnalysisAlgorithmsConfig.SaveConfigUtils import combine_tools_and_algorithms_ELjob
83 try:
84 combine_tools_and_algorithms_ELjob(combine_dictionaries=True, alg_file=self._algSequenceFile, output_file="full_config.json")
85 self.logger.info("Combining full config to full_config.json succeeded")
86
87 except Exception as e:
88 self.logger.warning(f'Combining full config failed with: {e}')
89 self.logger.warning('Please also check if "PrintConfiguration" is enabled in the text config.')
90 finally:
91 # the file may be missing if the configuration failed before writing it
92 if os.path.exists(self._algSequenceFile):
93 os.remove(self._algSequenceFile)
94

◆ _dumpAlgSequence()

python.EventLoopCPRunScript.EventLoopCPRunScript._dumpAlgSequence ( self)
protected

Definition at line 69 of file EventLoopCPRunScript.py.

69 def _dumpAlgSequence(self):
70 from AnalysisAlgorithmsConfig.SaveConfigUtils import save_algs_from_sequence_ELjob
71 import json
72 with open(self._algSequenceFile, 'w', encoding='utf-8') as seq_out_file:
73 output_dict = {}
74 try:
75 save_algs_from_sequence_ELjob(self.algSeq, output_dict)
76 json.dump(output_dict, seq_out_file, ensure_ascii=False, indent=4)
77 except Exception as e:
78 self.logger.warning(f'Dumping full config failed with: {e}')
79 self.logger.warning('Please also check if "PrintConfiguration" is enabled in the text config.')
80

◆ _flushOutput()

python.EventLoopCPRunScript.EventLoopCPRunScript._flushOutput ( )
staticprotected

Definition at line 161 of file EventLoopCPRunScript.py.

161 def _flushOutput():
162 sys.stdout.flush()
163 sys.stderr.flush()
164

◆ addCustomArguments()

python.EventLoopCPRunScript.EventLoopCPRunScript.addCustomArguments ( self)

Definition at line 15 of file EventLoopCPRunScript.py.

15 def addCustomArguments(self):
16 # add arguments here
17 derivedGroup = self.parser.add_argument_group('EventLoop specific arguments')
18 derivedGroup.add_argument('--direct-driver', dest='direct_driver',
19 action='store_true', help='Run the job with the direct driver')
20 derivedGroup.add_argument('--work-dir', dest='work_dir', nargs='?', const='workDir', default=None,
21 help='The work directory for the EL job. defaults to "workDir".')
22 derivedGroup.add_argument('--dump-full-config', dest='dump_full_config', action='store_true', help='Save the full CP configuration log to a json file. This can be useful for debugging purposes.')
23
24 expertGroup = self.parser.add_argument_group('Experts arguments')
25 expertGroup.add_argument('--run-perf-stat', dest='run_perf_stat', action='store_true', help='Run xAOD::PerfStats to get input branch access data. This is mostly useful for AMG experts wanting to understand branch access patterns.')
26 expertGroup.add_argument('--algorithm-timers', dest='algorithm_timers', action='store_true', help='Enable algorithm timers. This is mostly useful for AMG experts wanting to understand tool performance.')
27 expertGroup.add_argument('--algorithm-memory-monitoring', dest='algorithm_memory_monitoring', action='store_true', help='Enable algorithm memory monitoring. This is mostly useful for AMG experts wanting to understand tool memory usage. Note that this is imperfect and may in cases assign memory to the wrong algorithm.')
28 return
29

◆ configureAndSubmit()

python.EventLoopCPRunScript.EventLoopCPRunScript.configureAndSubmit ( self)
Do the full job configuration and submit the job.

Everything that loads ROOT (and with it the ROOT-python
dictionaries, which account for most of the configuration-time
memory) happens in here.  With the exec driver, driver.submit()
replaces the current process with the worker and never returns.

Definition at line 165 of file EventLoopCPRunScript.py.

165 def configureAndSubmit(self):
166 '''
167 Do the full job configuration and submit the job.
168
169 Everything that loads ROOT (and with it the ROOT-python
170 dictionaries, which account for most of the configuration-time
171 memory) happens in here. With the exec driver, driver.submit()
172 replaces the current process with the worker and never returns.
173 '''
174 self.setupConfig()
175 # importing ROOT has a long upfront time and high memory
176 # consumption, so we do it here
177 import ROOT
178 ROOT.xAOD.Init().ignore()
179 self.readSamples()
180 self.flags.lock()
181 self.printFlags()
182
183 self.job = ROOT.EL.Job()
184 self.job.sampleHandler(self.sampleHandler)
185 self.job.options().setDouble(ROOT.EL.Job.optFilesPerWorker, 100)
186 self.job.options().setDouble(ROOT.EL.Job.optMaxEvents, self.flags.Exec.MaxEvents)
187 self.job.options().setString(ROOT.EL.Job.optSubmitDirMode, 'unique-link')
188 self.job.options().setDouble(ROOT.EL.Job.optSkipEvents, self.flags.Exec.SkipEvents)
189
190 for alg in self.makeAlgSequence():
191 self.job.algsAdd(alg)
192 # Every requested output stream not going to the histogram file gets its own file: the
193 # default tree stream ANALYSIS only if something writes to it, plus e.g. the streams of
194 # Output.splitBySelection
195 histStreams = {ROOT.EL.Job.histogramStreamName}
196 if self.args.merge_output_files:
197 self.job.options().setString(ROOT.EL.Job.optStreamAliases, "ANALYSIS=" + ROOT.EL.Job.histogramStreamName)
198 histStreams.add('ANALYSIS')
199 for stream in [stream for stream in self.outputStreams if stream not in histStreams]:
200 self.job.outputAdd(ROOT.EL.OutputStream(stream))
201
202 if self.args.run_perf_stat:
203 self.job.options().setBool(ROOT.EL.Job.optXAODPerfStats, 1)
204 if self.args.algorithm_timers:
205 self.job.options().setBool(ROOT.EL.Job.optAlgorithmTimer, 1)
206 if self.args.algorithm_memory_monitoring:
207 self.job.options().setBool(ROOT.EL.Job.optAlgorithmMemoryMonitor, 1)
208
209 if self.args.dump_full_config:
210 self._dumpAlgSequence()
211
212 driver = ROOT.EL.DirectDriver() if self.args.direct_driver else ROOT.EL.ExecDriver()
213 # the exec driver replaces this process, so anything still sitting in
214 # the python output buffers would be lost without this flush
215 self._flushOutput()
216 driver.submit(self.job, self.submitDir)
217
virtual void lock()=0
Interface to allow an object to lock itself when made const in SG.

◆ getExitCode()

python.EventLoopCPRunScript.EventLoopCPRunScript.getExitCode ( self)
Exit code for the direct driver, where the job ran in this process.
For the exec driver the exit status of the worker process is used
instead, so that this process never has to load ROOT.

Definition at line 148 of file EventLoopCPRunScript.py.

148 def getExitCode(self):
149 '''
150 Exit code for the direct driver, where the job ran in this process.
151 For the exec driver the exit status of the worker process is used
152 instead, so that this process never has to load ROOT.
153 '''
154 import ROOT
155 statusCode = ROOT.EL.Driver.retrieve(self.submitDir)
156 if statusCode:
157 return 0
158 return 1
159

◆ makeAlgSequence()

python.EventLoopCPRunScript.EventLoopCPRunScript.makeAlgSequence ( self)

Definition at line 30 of file EventLoopCPRunScript.py.

30 def makeAlgSequence(self):
31 from AnaAlgorithm.AlgSequence import AlgSequence
32 from AnalysisAlgorithmsConfig.ConfigAccumulator import ConfigAccumulator
33 algSeq = AlgSequence()
34 self.logger.info("Configuring algorithms based on YAML file")
35 configSeq = self.config.configure()
36 self.logger.info("Configuring common services")
37 configAccumulator = ConfigAccumulator(flags=self.flags,
38 algSeq=algSeq,
39 noSystematics=self.args.no_systematics)
40 # EventLoop writes histograms to its histogram file whatever their stream,
41 # so they should not request the tree stream ANALYSIS
42 import ROOT
43 configAccumulator.setDefaultHistogramStream(ROOT.EL.Job.histogramStreamName)
44 self.logger.info("Configuring algorithms")
45 configSeq.fullConfigure(configAccumulator)
46 self.algSeq = algSeq
47 self.outputStreams = configAccumulator.outputStreams()
48 self.modifyAlgSequence()
49 return algSeq
50
bool configure(asg::AnaToolHandle< ITrigGlobalEfficiencyCorrectionTool > &tool, ToolHandleArray< IAsgElectronEfficiencyCorrectionTool > &electronEffToolsHandles, ToolHandleArray< IAsgElectronEfficiencyCorrectionTool > &electronSFToolsHandles, ToolHandleArray< CP::IMuonTriggerScaleFactors > &muonToolsHandles, ToolHandleArray< IAsgPhotonEfficiencyCorrectionTool > &photonEffToolsHandles, ToolHandleArray< IAsgPhotonEfficiencyCorrectionTool > &photonSFToolsHandles, const std::string &triggers, const std::map< std::string, std::string > &legsPerTool, unsigned long nToys, bool debug)

◆ moveOutputFiles()

python.EventLoopCPRunScript.EventLoopCPRunScript.moveOutputFiles ( self)

Definition at line 95 of file EventLoopCPRunScript.py.

95 def moveOutputFiles(self):
96 from pathlib import Path
97 import shutil
98 self.logger.info("Moving the analysis root file and the hist file to the top level.")
99 workDir = Path(self.args.work_dir) if self.args.work_dir else Path('workDir')
100 rootfileSymlink = (workDir / 'data-ANALYSIS' / f'{self.outputName}.root')
101 rootfilePath = rootfileSymlink.resolve()
102 histfileSymlink = (workDir / f'hist-{self.outputName}.root')
103 histfilePath = histfileSymlink.resolve()
104 currentDir = Path.cwd()
105 # move ntuple file if it exists
106 if rootfilePath.exists():
107 self.logger.info(f"Moving {rootfilePath} to {currentDir / f'{self.outputName}.root'}")
108 if rootfileSymlink.is_symlink(): # The check is needed to avoid FileNotFoundError if using direct driver
109 rootfileSymlink.unlink()
110 shutil.move(str(rootfilePath), str(currentDir / f"{self.outputName}.root"))
111 else:
112 self.logger.warning(f"Root file {rootfilePath} does not exist or merging is enabled, skipping move.")
113 #move histogram file if it exists
114 if histfilePath.exists():
115 self.logger.info(f"Moving {histfilePath} to {currentDir / f'hist-{self.outputName}.root'}")
116 if histfileSymlink.is_symlink(): # The check is needed to avoid FileNotFoundError if using direct driver
117 histfileSymlink.unlink()
118 shutil.move(str(histfilePath), str(currentDir / f"hist-{self.outputName}.root"))
119 else:
120 self.logger.warning(f"Histogram file {histfilePath} does not exist or merging, skipping move.")
121 # move the other output stream files, named as in AthenaCPRunScript; taken from disk as
122 # the streams are only known in the job process, which the exec driver forks off
123 for streamDir in sorted(workDir.glob('data-*')):
124 stream = streamDir.name.removeprefix('data-')
125 if stream == 'ANALYSIS':
126 continue
127 streamSymlink = streamDir / f'{self.outputName}.root'
128 streamPath = streamSymlink.resolve()
129 target = currentDir / f"{self.outputName}_{stream.removeprefix('ANALYSIS_')}.root"
130 if streamPath.exists():
131 self.logger.info(f"Moving {streamPath} to {target}")
132 if streamSymlink.is_symlink():
133 streamSymlink.unlink()
134 shutil.move(str(streamPath), str(target))
135 else:
136 self.logger.warning(f"Root file {streamPath} does not exist, skipping move.")
137
138 newHistFile = currentDir / f"hist-{self.outputName}.root"
139 # rename merged hist-ntuple to output_name.root
140 if self.args.merge_output_files and newHistFile.exists():
141 self.logger.info(f"renaming the hist-{self.outputName}.root to {self.outputName}.root")
142 newHistFile.rename(currentDir / f"{self.outputName}.root")
143

◆ readSamples()

python.EventLoopCPRunScript.EventLoopCPRunScript.readSamples ( self)

Definition at line 51 of file EventLoopCPRunScript.py.

51 def readSamples(self):
52 import ROOT
53 self.sampleHandler = ROOT.SH.SampleHandler()
54 sampleFiles = ROOT.SH.SampleLocal(f"{self.outputName}")
55 self.logger.info("Adding files to the sample handler")
56 for file in self.inputList:
57 sampleFiles.add(file)
58 self.sampleHandler.add(sampleFiles)
59
bool add(const std::string &hname, TKey *tobj)
Definition fastadd.cxx:55

◆ run()

python.EventLoopCPRunScript.EventLoopCPRunScript.run ( self)

Definition at line 256 of file EventLoopCPRunScript.py.

256 def run(self):
257 # only the argument parsing happens before the fork, see runInChildProcess()
258 self.setupArguments()
259
260 if self.args.direct_driver:
261 try:
262 self.configureAndSubmit()
263 except Exception:
264 import traceback
265 traceback.print_exc()
266 self.logger.error('Job submission failed')
267 exitCode = self.getExitCode()
268 else:
269 exitCode = self.runInChildProcess()
270
271 if self.args.dump_full_config:
272 self._combineFullConfig()
273
274 if self.args.work_dir is None: # move output if work_dir is not used
275 self.moveOutputFiles()
276
277 sys.exit(exitCode)
int run(int argc, char *argv[])

◆ runInChildProcess()

python.EventLoopCPRunScript.EventLoopCPRunScript.runInChildProcess ( self)
Run configureAndSubmit() in a forked child process and return the
exit code of the job.

The fork has to happen here, before anything imports ROOT or reads
the input file metadata through the flags.  The exec driver frees the
configuration-time memory by replacing the configuring process with
the worker, and that only helps if the process that stays behind to
wait for the job (this one) never loaded ROOT in the first place.
Forking after the configuration would just keep all that memory alive
in the waiting parent for the duration of the job.

The worker (eventloop_exec_worker) runs all job segments, retrieves
the output and exits non-zero on any failure, so its exit status is
the exit status of the job.

Definition at line 218 of file EventLoopCPRunScript.py.

218 def runInChildProcess(self):
219 '''
220 Run configureAndSubmit() in a forked child process and return the
221 exit code of the job.
222
223 The fork has to happen here, before anything imports ROOT or reads
224 the input file metadata through the flags. The exec driver frees the
225 configuration-time memory by replacing the configuring process with
226 the worker, and that only helps if the process that stays behind to
227 wait for the job (this one) never loaded ROOT in the first place.
228 Forking after the configuration would just keep all that memory alive
229 in the waiting parent for the duration of the job.
230
231 The worker (eventloop_exec_worker) runs all job segments, retrieves
232 the output and exits non-zero on any failure, so its exit status is
233 the exit status of the job.
234 '''
235 self._flushOutput()
236 if (pid := os.fork()) == 0: # child process
237 try:
238 self.configureAndSubmit()
239 # the exec driver never returns from submit, so getting here
240 # means the process was not replaced
241 self.logger.error('Job submission returned without running the job')
242 except Exception:
243 import traceback
244 traceback.print_exc()
245 self.logger.error('Job configuration or submission failed')
246 self._flushOutput()
247 os._exit(1)
248
249 _, status = os.waitpid(pid, 0) # parent waits for the job to finish
250 exitCode = os.waitstatus_to_exitcode(status)
251 if exitCode != 0:
252 self.logger.error(f'Job in child process failed with exit code {exitCode}')
253 return 1
254 return 0
255

◆ submitDir()

python.EventLoopCPRunScript.EventLoopCPRunScript.submitDir ( self)

Definition at line 145 of file EventLoopCPRunScript.py.

145 def submitDir(self):
146 return self.args.work_dir if self.args.work_dir else 'workDir'
147

Member Data Documentation

◆ _algSequenceFile

python.EventLoopCPRunScript.EventLoopCPRunScript._algSequenceFile = "_alg_sequence.json"
staticprotected

Definition at line 67 of file EventLoopCPRunScript.py.

◆ algSeq

python.EventLoopCPRunScript.EventLoopCPRunScript.algSeq = None

Definition at line 11 of file EventLoopCPRunScript.py.

◆ inputList

python.EventLoopCPRunScript.EventLoopCPRunScript.inputList

Definition at line 56 of file EventLoopCPRunScript.py.

◆ job

python.EventLoopCPRunScript.EventLoopCPRunScript.job = ROOT.EL.Job()

Definition at line 183 of file EventLoopCPRunScript.py.

◆ outputStreams

list python.EventLoopCPRunScript.EventLoopCPRunScript.outputStreams = []

Definition at line 12 of file EventLoopCPRunScript.py.

◆ sampleHandler

python.EventLoopCPRunScript.EventLoopCPRunScript.sampleHandler = ROOT.SH.SampleHandler()

Definition at line 53 of file EventLoopCPRunScript.py.

◆ submitDir

python.EventLoopCPRunScript.EventLoopCPRunScript.submitDir

Definition at line 216 of file EventLoopCPRunScript.py.


The documentation for this class was generated from the following file: