| The Gaudi Framework v29r1 (5cd95fa2)

- Main Page
- Related Pages
- Modules
- Namespaces
- Classes
- Files

- File List
- File Members

gaudirun.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2
3 import os
4 import sys
5 from tempfile import mkstemp
6
7
8 def getArgsWithoutoProfilerInfo(args):
9 """
10 Remove from the arguments the presence of the profiler and its output in
11 order to relaunch the script w/o infinite loops.
12
13 >>> getArgsWithoutoProfilerInfo(['--profilerName', 'igprof', 'myopts.py'])
14 ['myopts.py']
15
16 >>> getArgsWithoutoProfilerInfo(['--profilerName=igprof', 'myopts.py'])
17 ['myopts.py']
18
19 >>> getArgsWithoutoProfilerInfo(['--profilerName', 'igprof', '--profilerExtraOptions', 'a b c', 'myopts.py'])
20 ['myopts.py']
21
22 >>> getArgsWithoutoProfilerInfo(['--profilerName', 'igprof', '--options', 'a b c', 'myopts.py'])
23 ['--options', 'a b c', 'myopts.py']
24 """
25 newargs = []
26 args = list(args) # make a temp copy
27 while args:
28 o = args.pop(0)
29 if o.startswith('--profile'):
30 if '=' not in o:
31 args.pop(0)
32 else:
33 newargs.append(o)
34 return newargs
35
36
37 def setLibraryPreload(newpreload):
38 ''' Adds a list of libraries to LD_PRELOAD '''
39 preload = os.environ.get("LD_PRELOAD", "")
40 if preload:
41 preload = preload.replace(" ", ":").split(":")
42 else:
43 preload = []
44
45 for libname in set(preload).intersection(newpreload):
46 logging.warning("Ignoring preload of library %s because it is "
47 "already in LD_PRELOAD.", libname)
48
49 to_load = [libname
50 for libname in newpreload
51 if libname not in set(preload)]
52
53 if to_load:
54 preload += to_load
55 preload = ":".join(preload)
56 os.environ["LD_PRELOAD"] = preload
57 logging.info("Setting LD_PRELOAD='%s'", preload)
58
59 return to_load
60
61
62 def rationalizepath(path):
63 '''
64 Convert the given path to a real path if the pointed file exists, otherwise
65 just normalize it.
66 '''
67 path = os.path.normpath(os.path.expandvars(path))
68 if os.path.exists(path):
69 path = os.path.realpath(path)
70 return path
71
72
73 # variable used to keep alive the temporary option files extracted
74 # from the .qmt
75 _qmt_tmp_opt_files = []
76
77
78 def getArgsFromQmt(qmtfile):
79 '''
80 Given a .qmt file, return the command line arguments of the corresponding
81 test.
82 '''
83 from xml.etree import ElementTree as ET
84 global _qmt_tmp_opt_files
85 # parse the .qmt file and extract args and options
86 qmt = ET.parse(qmtfile)
87 args = [a.text for a in qmt.findall("argument[@name='args']//text")]
88 options = qmt.find("argument[@name='options']/text")
89
90 if options is not None: # options need to be dumped in a temporary file
91 from tempfile import NamedTemporaryFile
92 import re
93 if re.search(r"from\s+Gaudi.Configuration\s+import\s+\*"
94 r"|from\s+Configurables\s+import", options.text):
95 tmp_opts = NamedTemporaryFile(suffix='.py')
96 else:
97 tmp_opts = NamedTemporaryFile(suffix='.opts')
98 tmp_opts.write(options.text)
99 tmp_opts.flush()
100 args.append(tmp_opts.name)
101 _qmt_tmp_opt_files.append(tmp_opts)
102
103 # relative paths in a .qmt are rooted in the qmtest directory, so
104 # - find where the .qmt lives
105 qmtfile = os.path.abspath(qmtfile)
106 if 'qmtest' in qmtfile.split(os.path.sep):
107 # this return the path up to the 'qmtest' entry in qmtfile
108 testdir = qmtfile
109 while os.path.basename(testdir) != 'qmtest':
110 testdir = os.path.dirname(testdir)
111 else:
112 testdir = '.'
113 # - temporarily switch to that directory and rationalize the paths
114 old_cwd = os.getcwd()
115 os.chdir(testdir)
116 args = map(rationalizepath, args)
117 os.chdir(old_cwd)
118
119 return args
120
121
122 #---------------------------------------------------------------------
123 if __name__ == "__main__":
124 # ensure that we (and the subprocesses) use the C standard localization
125 if os.environ.get('LC_ALL') != 'C':
126 print '# setting LC_ALL to "C"'
127 os.environ['LC_ALL'] = 'C'
128
129 from optparse import OptionParser
130 parser = OptionParser(usage="%prog [options] <opts_file> ...")
131 parser.add_option("-n", "--dry-run", action="store_true",
132 help="do not run the application, just parse option files")
133 parser.add_option("-p", "--pickle-output", action="store", type="string",
134 metavar="FILE",
135 help="DEPRECATED: use '--output file.pkl' instead. Write "
136 "the parsed options as a pickle file (static option "
137 "file)")
138 parser.add_option("-v", "--verbose", action="store_true",
139 help="print the parsed options")
140 parser.add_option("--old-opts", action="store_true",
141 help="format printed options in old option files style")
142 parser.add_option("--all-opts", action="store_true",
143 help="print all the option (even if equal to default)")
144 # GaudiPython Parallel Mode Option
145 # Argument must be an integer in range [ -1, sys_cpus ]
146 # -1 : All available cpus
147 # 0 : Serial Mode (traditional gaudirun)
148 # n>0 : parallel with n cpus (n <= sys_cpus)
149 parser.add_option("--ncpus", action="store", type="int", default=0,
150 help="start the application in parallel mode using NCPUS processes. "
151 "0 => serial mode (default), -1 => use all CPUs")
152
153 def option_cb(option, opt, value, parser):
154 """Add the option line to a list together with its position in the
155 argument list.
156 """
157 parser.values.options.append((len(parser.largs), value))
158 parser.add_option("--option", action="callback", callback=option_cb,
159 type="string", nargs=1,
160 help="add a single line (Python) option to the configuration. "
161 "All options lines are executed, one after the other, in "
162 "the same context.")
163 parser.add_option("--no-conf-user-apply", action="store_true",
164 help="disable the automatic application of configurable "
165 "users (for backward compatibility)")
166 parser.add_option("--old-conf-user-apply", action="store_true",
167 help="use the old logic when applying ConfigurableUsers "
168 "(with bug #103803) [default]")
169 parser.add_option("--new-conf-user-apply", action="store_false",
170 dest="old_conf_user_apply",
171 help="use the new (correct) logic when applying "
172 "ConfigurableUsers (fixed bug #103803), can be "
173 "turned on also with the environment variable "
174 "GAUDI_FIXED_APPLY_CONF")
175 parser.add_option("-o", "--output", action="store", type="string",
176 help="dump the configuration to a file. The format of "
177 "the options is determined by the extension of the "
178 "file name: .pkl = pickle, .py = python, .opts = "
179 "old style options. The python format cannot be "
180 "used to run the application and it contains the "
181 "same dictionary printed with -v")
182 parser.add_option("--post-option", action="append", type="string",
183 dest="post_options",
184 help="Python options to be executed after the ConfigurableUser "
185 "are applied. "
186 "All options lines are executed, one after the other, in "
187 "the same context.")
188 parser.add_option("--debug", action="store_true",
189 help="enable some debug print-out")
190 parser.add_option("--gdb", action="store_true",
191 help="attach gdb")
192 parser.add_option("--printsequence", action="store_true",
193 help="print the sequence")
194 if not sys.platform.startswith("win"):
195 # These options can be used only on unix platforms
196 parser.add_option("-T", "--tcmalloc", action="store_true",
197 help="Use the Google malloc replacement. The environment "
198 "variable TCMALLOCLIB can be used to specify a different "
199 "name for the library (the default is libtcmalloc.so)")
200 parser.add_option("--preload", action="append",
201 help="Allow pre-loading of special libraries (e.g. Google "
202 "profiling libraries).")
203 # Option to use a profiler
204 parser.add_option("--profilerName", type="string",
205 help="Select one profiler among: igprofPerf, igprofMem and valgrind<toolname>")
206
207 # Option to specify the filename where to collect the profiler's output
208 parser.add_option("--profilerOutput", type="string",
209 help="Specify the name of the output file for the profiler output")
210
211 # Option to specify the filename where to collect the profiler's output
212 parser.add_option("--profilerExtraOptions", type="string",
213 help="Specify additional options for the profiler. The '--' string should be expressed as '__' (--my-opt becomes __my-opt)")
214
215 parser.add_option('--use-temp-opts', action='store_true',
216 help='when this option is enabled, the options are parsed'
217 ' and stored in a temporary file, then the job is '
218 'restarted using that file as input (to save '
219 'memory)')
220 parser.add_option("--run-info-file", type="string",
221 help="Save gaudi process information to the file specified (in JSON format)")
222
223 parser.set_defaults(options=[],
224 tcmalloc=False,
225 profilerName='',
226 profilerOutput='',
227 profilerExtraOptions='',
228 preload=[],
229 ncpus=None,
230 # the old logic can be turned off with an env variable
231 old_conf_user_apply='GAUDI_FIXED_APPLY_CONF' not in os.environ,
232 run_info_file=None)
233
234 # replace .qmt files in the command line with their contained args
235 argv = []
236 for a in sys.argv[1:]:
237 if a.endswith('.qmt') and os.path.exists(a):
238 argv.extend(getArgsFromQmt(a))
239 else:
240 argv.append(a)
241 if argv != sys.argv[1:]:
242 print '# Running', sys.argv[0], 'with arguments', argv
243
244 opts, args = parser.parse_args(args=argv)
245
246 # Check consistency of options
247
248 # Parallel Option ---------------------------------------------------------
249 if opts.ncpus:
250 from multiprocessing import cpu_count
251 sys_cpus = cpu_count()
252 if opts.ncpus > sys_cpus:
253 s = "Invalid value : --ncpus : only %i cpus available" % sys_cpus
254 parser.error(s)
255 elif opts.ncpus < -1:
256 s = "Invalid value : --ncpus must be integer >= -1"
257 parser.error(s)
258 else:
259 # FIXME: is it really needed to set it to None if it is 0 or False?
260 opts.ncpus = None
261
262 # configure the logging
263 import logging
264 from GaudiKernel.ProcessJobOptions import (InstallRootLoggingHandler,
265 PrintOff)
266
267 if opts.old_opts:
268 prefix = "// "
269 else:
270 prefix = "# "
271 level = logging.INFO
272 if opts.debug:
273 level = logging.DEBUG
274 InstallRootLoggingHandler(prefix, level=level, with_time=opts.debug)
275 root_logger = logging.getLogger()
276
277 # tcmalloc support
278 if opts.tcmalloc:
279 opts.preload.insert(0, os.environ.get("TCMALLOCLIB", "libtcmalloc.so"))
280 # allow preloading of libraries
281 if opts.preload:
282 preload = os.environ.get("LD_PRELOAD", "")
283 if preload:
284 preload = preload.replace(" ", ":").split(":")
285 else:
286 preload = []
287 for libname in set(preload).intersection(opts.preload):
288 logging.warning("Ignoring preload of library %s because it is "
289 "already in LD_PRELOAD.", libname)
290 to_load = [libname
291 for libname in opts.preload
292 if libname not in set(preload)]
293 if to_load:
294 preload += to_load
295 preload = ":".join(preload)
296 os.environ["LD_PRELOAD"] = preload
297 logging.info("Restarting with LD_PRELOAD='%s'", preload)
298 # remove the --tcmalloc option from the arguments
299 # FIXME: the --preload arguments will issue a warning but it's tricky to remove them
300 args = [a for a in sys.argv if a !=
301 '-T' and not '--tcmalloc'.startswith(a)]
302 os.execv(sys.executable, [sys.executable] + args)
303
304 # Profiler Support ------
305 if opts.profilerName:
306 profilerName = opts.profilerName
307 profilerExecName = ""
308 profilerOutput = opts.profilerOutput or (profilerName + ".output")
309
310 # To restart the application removing the igprof option and prepending the string
311 args = getArgsWithoutoProfilerInfo(sys.argv)
312
313 igprofPerfOptions = "-d -pp -z -o igprof.pp.gz".split()
314
315 profilerOptions = ""
316 if profilerName == "igprof":
317 if not opts.profilerOutput:
318 profilerOutput += ".profile.gz"
319 profilerOptions = "-d -z -o %s" % profilerOutput
320 profilerExecName = "igprof"
321
322 elif profilerName == "igprofPerf":
323 if not opts.profilerOutput:
324 profilerOutput += ".pp.gz"
325 profilerOptions = "-d -pp -z -o %s" % profilerOutput
326 profilerExecName = "igprof"
327
328 elif profilerName == "igprofMem":
329 if not opts.profilerOutput:
330 profilerOutput += ".mp.gz"
331 profilerOptions = "-d -mp -z -o %s" % profilerOutput
332 profilerExecName = "igprof"
333
334 elif "valgrind" in profilerName:
335 # extract the tool
336 if not opts.profilerOutput:
337 profilerOutput += ".log"
338 toolname = profilerName.replace('valgrind', '')
339 outoption = "--log-file"
340 if toolname in ("massif", "callgrind", "cachegrind"):
341 outoption = "--%s-out-file" % toolname
342 profilerOptions = "--tool=%s %s=%s" % (
343 toolname, outoption, profilerOutput)
344 profilerExecName = "valgrind"
345
346 elif profilerName == "jemalloc":
347 opts.preload.insert(0, os.environ.get(
348 "JEMALLOCLIB", "libjemalloc.so"))
349 os.environ['MALLOC_CONF'] = "prof:true,prof_leak:true"
350 else:
351 root_logger.warning("Profiler %s not recognized!" % profilerName)
352
353 # Add potential extra options
354 if opts.profilerExtraOptions != "":
355 profilerExtraOptions = opts.profilerExtraOptions
356 profilerExtraOptions = profilerExtraOptions.replace("__", "--")
357 profilerOptions += " %s" % profilerExtraOptions
358
359 # now we look for the full path of the profiler: is it really there?
360 if profilerExecName:
361 import distutils.spawn
362 profilerPath = distutils.spawn.find_executable(profilerExecName)
363 if not profilerPath:
364 root_logger.error("Cannot locate profiler %s" %
365 profilerExecName)
366 sys.exit(1)
367
368 root_logger.info("------ Profiling options are on ------ \n"
369 " o Profiler: %s\n"
370 " o Options: '%s'.\n"
371 " o Output: %s" % (profilerExecName or profilerName, profilerOptions, profilerOutput))
372
373 # allow preloading of libraries
374 # That code need to be acsracted from above
375 to_reload = []
376 if opts.preload:
377 to_reload = setLibraryPreload(opts.preload)
378
379 if profilerExecName:
380 # We profile python
381 profilerOptions += " python"
382
383 # now we have all the ingredients to prepare our command
384 arglist = [profilerPath] + profilerOptions.split() + args
385 arglist = [a for a in arglist if a != '']
386 # print profilerPath
387 # for arg in arglist:
388 # print arg
389 os.execv(profilerPath, arglist)
390 else:
391 arglist = [a for a in sys.argv if not a.startswith("--profiler")]
392 os.execv(sys.executable, [sys.executable] + arglist)
393
394 # End Profiler Support ------
395
396 if opts.pickle_output:
397 if opts.output:
398 root_logger.error(
399 "Conflicting options: use only --pickle-output or --output")
400 sys.exit(1)
401 else:
402 root_logger.warning(
403 "--pickle-output is deprecated, use --output instead")
404 opts.output = opts.pickle_output
405
406 from Gaudi.Main import gaudimain
407 c = gaudimain()
408
409 # Prepare the "configuration script" to parse (like this it is easier than
410 # having a list with files and python commands, with an if statements that
411 # decides to do importOptions or exec)
412 options = ["importOptions(%r)" % f for f in args]
413 # The option lines are inserted into the list of commands using their
414 # position on the command line
415 optlines = list(opts.options)
416 # this allows to avoid to have to care about corrections of the positions
417 optlines.reverse()
418 for pos, l in optlines:
419 options.insert(pos, l)
420
421 # prevent the usage of GaudiPython
422 class FakeModule(object):
423 def __init__(self, exception):
424 self.exception = exception
425
426 def __getattr__(self, *args, **kwargs):
427 raise self.exception
428 sys.modules["GaudiPython"] = FakeModule(
429 RuntimeError("GaudiPython cannot be used in option files"))
430
431 # when the special env GAUDI_TEMP_OPTS_FILE is set, it overrides any
432 # option(file) on the command line
433 if 'GAUDI_TEMP_OPTS_FILE' in os.environ:
434 options = ['importOptions(%r)' % os.environ['GAUDI_TEMP_OPTS_FILE']]
435 PrintOff(100)
436
437 # "execute" the configuration script generated (if any)
438 if options:
439 g = {}
440 l = {}
441 exec "from Gaudi.Configuration import *" in g, l
442 for o in options:
443 logging.debug(o)
444 exec o in g, l
445
446 import GaudiKernel.Proxy.Configurable
447 if opts.no_conf_user_apply:
448 logging.info("Disabling automatic apply of ConfigurableUser")
449 # pretend that they have been already applied
450 GaudiKernel.Proxy.Configurable._appliedConfigurableUsers_ = True
451
452 # This need to be done before dumping
453 if opts.old_conf_user_apply:
454 from GaudiKernel.Proxy.Configurable import applyConfigurableUsers_old as applyConfigurableUsers
455 else:
456 from GaudiKernel.Proxy.Configurable import applyConfigurableUsers
457 applyConfigurableUsers()
458
459 # Options to be processed after applyConfigurableUsers
460 if opts.post_options:
461 g = {}
462 l = {}
463 exec "from Gaudi.Configuration import *" in g, l
464 for o in opts.post_options:
465 logging.debug(o)
466 exec o in g, l
467
468 if 'GAUDI_TEMP_OPTS_FILE' in os.environ:
469 os.remove(os.environ['GAUDI_TEMP_OPTS_FILE'])
470 opts.use_temp_opts = False
471
472 if opts.verbose and not opts.use_temp_opts:
473 c.printconfig(opts.old_opts, opts.all_opts)
474 if opts.output:
475 c.writeconfig(opts.output, opts.all_opts)
476
477 if opts.use_temp_opts:
478 fd, tmpfile = mkstemp('.opts')
479 os.close(fd)
480 c.writeconfig(tmpfile, opts.all_opts)
481 os.environ['GAUDI_TEMP_OPTS_FILE'] = tmpfile
482 logging.info('Restarting from pre-parsed options')
483 os.execv(sys.executable, [sys.executable] + sys.argv)
484
485 c.printsequence = opts.printsequence
486 if opts.printsequence:
487 if opts.ncpus:
488 logging.warning(
489 "--printsequence not supported with --ncpus: ignored")
490 elif opts.dry_run:
491 logging.warning(
492 "--printsequence not supported with --dry-run: ignored")
493
494 # re-enable the GaudiPython module
495 del sys.modules["GaudiPython"]
496
497 if not opts.dry_run:
498 # Do the real processing
499 retcode = c.run(opts.gdb, opts.ncpus)
500
501 # Now saving the run information pid, retcode and executable path to
502 # a file is requested
503 if opts.run_info_file:
504 import os
505 import json
506 run_info = {}
507 run_info["pid"] = os.getpid()
508 run_info["retcode"] = retcode
509 if os.path.exists('/proc/self/exe'):
510 # These options can be used only on unix platforms
511 run_info["exe"] = os.readlink('/proc/self/exe')
512
513 logging.info("Saving run info to: %s" % opts.run_info_file)
514 with open(opts.run_info_file, "w") as f:
515 json.dump(run_info, f)
516
517 sys.exit(retcode)
GaudiKernel.ProcessJobOptions.PrintOff
def PrintOff(step=1)
Definition: ProcessJobOptions.py:129
gaudirun.FakeModule.exception
exception
Definition: gaudirun.py:424
gaudirun.FakeModule.__init__
def __init__(self, exception)
Definition: gaudirun.py:423
gaudirun.option_cb
def option_cb(option, opt, value, parser)
Definition: gaudirun.py:153
gaudirun.rationalizepath
def rationalizepath(path)
Definition: gaudirun.py:62
GaudiKernel.Configurable.applyConfigurableUsers
def applyConfigurableUsers()
Definition: Configurable.py:1448
gaudirun.getArgsFromQmt
def getArgsFromQmt(qmtfile)
Definition: gaudirun.py:78
GaudiKernel.ProcessJobOptions
Definition: ProcessJobOptions.py:1
GaudiKernel.ProcessJobOptions.InstallRootLoggingHandler
def InstallRootLoggingHandler(prefix=None, level=None, stream=None, with_time=False)
Definition: ProcessJobOptions.py:116
Containers::map
struct GAUDI_API map
Parametrisation class for map-like implementation.
Definition: KeyedObjectManager.h:26
gaudirun.FakeModule
Definition: gaudirun.py:422
gaudirun.getArgsWithoutoProfilerInfo
def getArgsWithoutoProfilerInfo(args)
Definition: gaudirun.py:8
Gaudi.Main
Definition: Main.py:1
gaudirun.FakeModule.__getattr__
def __getattr__(self, args, kwargs)
Definition: gaudirun.py:426
Gaudi.Main.gaudimain
Definition: Main.py:163
gaudirun.setLibraryPreload
def setLibraryPreload(newpreload)
Definition: gaudirun.py:37