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