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