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