The Gaudi Framework  v30r3 (a5ef0a68)
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 
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 
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
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)
def __init__(self, exception)
Definition: gaudirun.py:423
def option_cb(option, opt, value, parser)
Definition: gaudirun.py:153
def rationalizepath(path)
Definition: gaudirun.py:62
def getArgsFromQmt(qmtfile)
Definition: gaudirun.py:78
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:8
def __getattr__(self, args, kwargs)
Definition: gaudirun.py:426
def setLibraryPreload(newpreload)
Definition: gaudirun.py:37