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