The Gaudi Framework  master (181af51f)
Loading...
Searching...
No Matches
Main.py
Go to the documentation of this file.
11import logging
12import os
13import sys
14from time import time
15
16from Gaudi import Configuration
17
18log = logging.getLogger(__name__)
19
20
21class BootstrapHelper(object):
22 class StatusCode(object):
23 def __init__(self, value):
24 self.value = value
25
26 def __bool__(self):
27 return self.value
28
29 __nonzero__ = __bool__
30
31 def isSuccess(self):
32 return self.value
33
34 def isFailure(self):
35 return not self.value
36
37 def ignore(self):
38 pass
39
40 class Property(object):
41 def __init__(self, value):
42 self.value = value
43
44 def __str__(self):
45 # TODO why bytes
46 return bytes(self.value).decode("utf-8")
47
48 toString = __str__
49
50 class AppMgr(object):
51 def __init__(self, ptr, lib):
52 self.ptr = ptr
53 self.lib = lib
54 self._as_parameter_ = ptr
55
56 def configure(self):
58 self.lib.py_bootstrap_fsm_configure(self.ptr)
59 )
60
61 def initialize(self):
63 self.lib.py_bootstrap_fsm_initialize(self.ptr)
64 )
65
66 def start(self):
67 return BootstrapHelper.StatusCode(self.lib.py_bootstrap_fsm_start(self.ptr))
68
69 def run(self, nevt):
71 self.lib.py_bootstrap_app_run(self.ptr, nevt)
72 )
73
74 def stop(self):
75 return BootstrapHelper.StatusCode(self.lib.py_bootstrap_fsm_stop(self.ptr))
76
77 def finalize(self):
79 self.lib.py_bootstrap_fsm_finalize(self.ptr)
80 )
81
82 def terminate(self):
84 self.lib.py_bootstrap_fsm_terminate(self.ptr)
85 )
86
87 def getService(self, name):
88 return self.lib.py_bootstrap_getService(self.ptr, name.encode("ascii"))
89
90 def setProperty(self, name, value):
92 self.lib.py_bootstrap_setProperty(
93 self.ptr, name.encode("ascii"), value.encode("ascii")
94 )
95 )
96
97 def getProperty(self, name):
99 self.lib.py_bootstrap_getProperty(self.ptr, name.encode("ascii"))
100 )
101
103 return self.lib.py_helper_printAlgsSequences(self.ptr)
104
105 def setOption(self, key, value):
106 self.lib.py_bootstrap_setOption(
107 self.ptr, key.encode("ascii"), value.encode("ascii")
108 )
109
110 def __init__(self):
111 from ctypes import RTLD_GLOBAL, PyDLL, c_bool, c_char_p, c_int, c_void_p, util
112
113 # Helper class to avoid void* to int conversion
114 # (see http://stackoverflow.com/questions/17840144)
115
116 class IInterface_p(c_void_p):
117 def __repr__(self):
118 return "IInterface_p(0x%x)" % (0 if self.value is None else self.value)
119
120 self.log = logging.getLogger("BootstrapHelper")
121 libname = util.find_library("GaudiKernel") or "libGaudiKernel.so"
122 self.log.debug("loading GaudiKernel (%s)", libname)
123
124 # FIXME: note that we need PyDLL instead of CDLL if the calls to
125 # Python functions are not protected with the GIL.
126 self.lib = gkl = PyDLL(libname, mode=RTLD_GLOBAL)
127
128 functions = [
129 ("createApplicationMgr", IInterface_p, []),
130 ("getService", IInterface_p, [IInterface_p, c_char_p]),
131 ("setProperty", c_bool, [IInterface_p, c_char_p, c_char_p]),
132 ("getProperty", c_char_p, [IInterface_p, c_char_p]),
133 ("setOption", None, [IInterface_p, c_char_p, c_char_p]),
134 ("ROOT_VERSION_CODE", c_int, []),
135 ]
136
137 for name, restype, argtypes in functions:
138 f = getattr(gkl, "py_bootstrap_%s" % name)
139 f.restype, f.argtypes = restype, argtypes
140 # create a delegate method if not already present
141 # (we do not want to use hasattr because it calls "properties")
142 if name not in self.__class__.__dict__:
143 setattr(self, name, f)
144
145 for name in (
146 "configure",
147 "initialize",
148 "start",
149 "stop",
150 "finalize",
151 "terminate",
152 ):
153 f = getattr(gkl, "py_bootstrap_fsm_%s" % name)
154 f.restype, f.argtypes = c_bool, [IInterface_p]
155 gkl.py_bootstrap_app_run.restype = c_bool
156 gkl.py_bootstrap_app_run.argtypes = [IInterface_p, c_int]
157
158 gkl.py_helper_printAlgsSequences.restype = None
159 gkl.py_helper_printAlgsSequences.argtypes = [IInterface_p]
160
162 ptr = self.lib.py_bootstrap_createApplicationMgr()
163 return self.AppMgr(ptr, self.lib)
164
165 @property
167 return self.lib.py_bootstrap_ROOT_VERSION_CODE()
168
169 @property
170 def ROOT_VERSION(self):
171 root_version_code = self.ROOT_VERSION_CODE
172 a = root_version_code >> 16 & 0xFF
173 b = root_version_code >> 8 & 0xFF
174 c = root_version_code & 0xFF
175 return (a, b, c)
176
177
178def _getAllOpts_old(explicit_defaults=False):
179 """
180 Return all options from the old configuration system as a dictionary.
181
182 If explicit_defaults is true, include default values of unset properties in the dictionary.
183 """
184 from itertools import chain
185
186 from GaudiKernel.Proxy.Configurable import Configurable, getNeededConfigurables
187
188 old_opts = {}
189
190 # Keep track of which algorithms we have already processed to avoid
191 # computing them many times
192 done_conf = set()
193 # More algorithms may be generated when we call "getValuedProperties" so we
194 # may need a few iterations before we get the full list
195 # (see GaudiConfig.ControlFlow)
196 while True:
197 needed_conf = [n for n in getNeededConfigurables() if n not in done_conf]
198 if not needed_conf:
199 break
200
201 for n in needed_conf:
202 done_conf.add(n)
203 c = Configurable.allConfigurables[n]
204 items = getattr(c, "getValuedProperties", dict)().items()
205 if explicit_defaults:
206 items = chain(c.getDefaultProperties().items(), items)
207 for p, v in items:
208 # Note: AthenaCommon.Configurable does not have Configurable.PropertyReference
209 if hasattr(Configurable, "PropertyReference") and isinstance(
210 v, Configurable.PropertyReference
211 ):
212 # this is done in "getFullName", but the exception is ignored,
213 # so we do it again to get it
214 v = v.__resolve__()
215 if isinstance(v, str):
216 # properly escape quotes in the string (see gaudi/Gaudi#78)
217 v = '"%s"' % v.replace('"', '\\"')
218 elif hasattr(v, "__opt_value__"):
219 v = v.__opt_value__()
220 old_opts[".".join((n, p))] = str(v)
221
222 return old_opts
223
224
225def getAllOpts(explicit_defaults=False):
226 """
227 Return all options from the old and new configuration system as a dictionary.
228
229 If explicit_defaults is true, include default values of unset properties in the dictionary.
230 """
231 import GaudiConfig2
232
233 # We need to run normal (without defaults) collection first (regardless of explicit_defaults)
234 # as the conflicts detection makes sense only for explicitly set options
235 old_opts = _getAllOpts_old(False)
236 opts = GaudiConfig2.all_options(False)
237
238 conflicts = [n for n in set(opts).intersection(old_opts) if opts[n] != old_opts[n]]
239 if conflicts:
240 conflicts.sort()
241 log.error("Some properties are set in old and new style configuration")
242 log.warning("name: old -> new")
243 for n in conflicts:
244 log.warning("%s: %s -> %s", n, old_opts[n], opts[n])
245 sys.exit(10)
246
247 opts.update(old_opts)
248
249 if explicit_defaults:
250 # If we are asked to print also the defaults, we collect everything
251 # and blindly merge to make sure we have the full set of configurables
252 all_opts = _getAllOpts_old(True)
253 all_opts.update(GaudiConfig2.all_options(True))
254 # the we override the dictionary with the explicitly set options
255 # (that leaves the defaults untouched and the set options with the
256 # correct value)
257 all_opts.update(opts)
258 opts = all_opts
259
260 return opts
261
262
263def toOpt(value):
264 """
265 Helper to convert values to old .opts format.
266
267 >>> print(toOpt('some "text"'))
268 "some \\"text\\""
269 >>> print(toOpt('first\\nsecond'))
270 "first
271 second"
272 >>> print(toOpt({'a': [1, 2, '3']}))
273 {"a": [1, 2, "3"]}
274 """
275 if isinstance(value, str):
276 return '"{0}"'.format(value.replace('"', '\\"'))
277 elif isinstance(value, dict):
278 return "{{{0}}}".format(
279 ", ".join("{0}: {1}".format(toOpt(k), toOpt(v)) for k, v in value.items())
280 )
281 elif hasattr(value, "__iter__"):
282 return "[{0}]".format(", ".join(map(toOpt, value)))
283 else:
284 return repr(value)
285
286
287def parseOpt(s):
288 """
289 Helper to parse option strings to Python values.
290
291 Ideally it should just be "eval", but the string parser of Gaudi
292 is different from the Python one, so we get string options that
293 cannot be just evaluated.
294
295 >>> print(parseOpt('123'))
296 123
297 >>> print(parseOpt('"some\\n\\\\"text\\\\""'))
298 some
299 "text"
300 >>> print(parseOpt(''))
301 <BLANKLINE>
302
303 (see gaudi/Gaudi#78)
304 """
305 import re
306
307 quoted_string = re.compile(r'^"(.*)"$', re.DOTALL)
308 # FIXME: this is needed because we cannot use repr for strings
309 # (see gaudi/Gaudi#78)
310 if not s: # pass through empty strings
311 return s
312 m = quoted_string.match(s)
313 if m:
314 return m.group(1).replace('\\"', '"')
315 return eval(s)
316
317
318class gaudimain(object):
319 def __init__(self):
320 from Configurables import ApplicationMgr
321
322 appMgr = ApplicationMgr()
323 if os.environ.get("GAUDIAPPNAME"):
324 appMgr.AppName = str(os.environ["GAUDIAPPNAME"])
325 if os.environ.get("GAUDIAPPVERSION"):
326 appMgr.AppVersion = str(os.environ["GAUDIAPPVERSION"])
327 self.log = logging.getLogger(__name__)
328 self.printsequence = False
329 self.application = "Gaudi::Application"
330
332 # ---------------------------------------------------
333 # set up Logging
334 # ----------------
335 # from multiprocessing import enableLogging, getLogger
336 import multiprocessing
337
338 # preliminaries for handlers/output files, etc.
339 from time import ctime
340
341 datetime = ctime()
342 datetime = datetime.replace(" ", "_")
343 outfile = open("gaudirun-%s.log" % (datetime), "w")
344 # two handlers, one for a log file, one for terminal
345 streamhandler = logging.StreamHandler(stream=outfile)
346 console = logging.StreamHandler()
347 # create formatter : the params in parentheses are variable names available via logging
348 formatter = logging.Formatter(
349 "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
350 )
351 # add formatter to Handler
352 streamhandler.setFormatter(formatter)
353 console.setFormatter(formatter)
354 # now, configure the logger
355 # enableLogging( level=0 )
356 # self.log = getLogger()
357 self.log = multiprocessing.log_to_stderr()
358 self.log.setLevel(logging.INFO)
359 self.log.name = "Gaudi/Main.py Logger"
360 self.log.handlers = []
361 # add handlers to logger : one for output to a file, one for console output
362 self.log.addHandler(streamhandler)
363 self.log.addHandler(console)
364 self.log.removeHandler(console)
365 # set level!!
366 self.log.setLevel = logging.INFO
367 # ---------------------------------------------------
368
369 def generatePyOutput(self, all=False):
370 import re
371 from collections import defaultdict
372 from pprint import pformat
373
374 optDict = defaultdict(dict)
375 allOpts = getAllOpts(all)
376 for key in allOpts:
377 c, p = key.rsplit(".", 1)
378 optDict[c][p] = parseOpt(allOpts[key])
379 formatted = pformat(dict(optDict))
380 # undo splitting of strings on multiple lines
381
382 return re.sub(r'"\n +"', "", formatted, flags=re.MULTILINE)
383
384 def generateOptsOutput(self, all=False):
385 opts = getAllOpts(all)
386 keys = sorted(opts)
387 return "\n".join(
388 "{} = {};".format(key, toOpt(parseOpt(opts[key]))) for key in keys
389 )
390
391 def _writepickle(self, filename):
392 # --- Lets take the first file input file as the name of the pickle file
393 import pickle
394
395 output = open(filename, "wb")
396 # Dump only the the configurables that make sense to dump (not User ones)
397 from GaudiKernel.Proxy.Configurable import getNeededConfigurables
398
399 to_dump = {}
400 for n in getNeededConfigurables():
401 to_dump[n] = Configuration.allConfigurables[n]
402 pickle.dump(to_dump, output, -1)
403 output.close()
404
405 def printconfig(self, old_format=False, all=False):
406 msg = "Dumping all configurables and properties"
407 if not all:
408 msg += " (different from default)"
409 log.info(msg)
410 if old_format:
411 print(self.generateOptsOutput(all))
412 else:
413 print(self.generatePyOutput(all))
414 sys.stdout.flush()
415
416 def writeconfig(self, filename, all=False):
417 import json
418
419 write = {
420 ".pkl": lambda filename, all: self._writepickle(filename),
421 ".py": lambda filename, all: open(filename, "w").write(
422 self.generatePyOutput(all) + "\n"
423 ),
424 ".opts": lambda filename, all: open(filename, "w").write(
425 self.generateOptsOutput(all) + "\n"
426 ),
427 ".json": lambda filename, all: json.dump(
428 getAllOpts(all), open(filename, "w"), indent=2, sort_keys=True
429 ),
430 }
431 try:
432 import yaml
433
434 write[".yaml"] = lambda filename, all: yaml.safe_dump(
435 getAllOpts(all), open(filename, "w")
436 )
437 write[".yml"] = write[".yaml"]
438 except ImportError:
439 pass # yaml support is optional
440
441 from os.path import splitext
442
443 ext = splitext(filename)[1]
444 if ext in write:
445 write[ext](filename, all)
446 else:
447 log.error(
448 "Unknown file type '%s'. Must be any of %r.", ext, sorted(write.keys())
449 )
450 sys.exit(1)
451
452 # Instantiate and run the application.
453 # Depending on the number of CPUs (ncpus) specified, it start
454 def run(self, attach_debugger, ncpus=None):
455 if not ncpus:
456 # Standard sequential mode
457 result = self.runSerial(attach_debugger)
458 else:
459 # Otherwise, run with the specified number of cpus
460 result = self.runParallel(ncpus)
461 return result
462
463 def hookDebugger(self, debugger="gdb"):
464 import os
465
466 self.log.info("attaching debugger to PID " + str(os.getpid()))
467 pid = os.spawnvp(
468 os.P_NOWAIT, debugger, [debugger, "-q", "python", str(os.getpid())]
469 )
470
471 # give debugger some time to attach to the python process
472 import time
473
474 time.sleep(5)
475
476 # verify the process' existence (will raise OSError if failed)
477 os.waitpid(pid, os.WNOHANG)
478 os.kill(pid, 0)
479 return
480
481 def runSerial(self, attach_debugger):
482 try:
483 from GaudiKernel.Proxy.Configurable import expandvars
484 except ImportError:
485 # pass-through implementation if expandvars is not defined (AthenaCommon)
486 def expandvars(data):
487 return data
488
489 from GaudiKernel.Proxy.Configurable import Configurable
490
491 self.log.debug("runSerial: apply options")
492 conf_dict = expandvars(getAllOpts())
493 conf_dict["ApplicationMgr.JobOptionsType"] = '"NONE"'
494
495 if self.printsequence:
496 conf_dict["ApplicationMgr.PrintAlgsSequence"] = "true"
497
498 if hasattr(Configurable, "_configurationLocked"):
499 Configurable._configurationLocked = True
500
501 if attach_debugger:
502 self.hookDebugger()
503
504 self.log.debug("-" * 80)
505 self.log.debug("%s: running in serial mode", __name__)
506 self.log.debug("-" * 80)
507 sysStart = time()
508
509 import Gaudi
510
511 app = Gaudi.Application.create(self.application, conf_dict)
512 retcode = app.run()
513
514 sysTime = time() - sysStart
515 self.log.debug("-" * 80)
516 self.log.debug(
517 "%s: serial system finished, time taken: %5.4fs", __name__, sysTime
518 )
519 self.log.debug("-" * 80)
520
521 return retcode
522
523 def runParallel(self, ncpus):
525 import GaudiMP.GMPBase as gpp
526 from Gaudi.Configuration import Configurable
527
528 c = Configurable.allConfigurables
529 self.log.info("-" * 80)
530 self.log.info("%s: Parallel Mode : %i ", __name__, ncpus)
531 for name, value in [
532 ("platform", " ".join(os.uname())),
533 ("config", os.environ.get("BINARY_TAG") or os.environ.get("CMTCONFIG")),
534 ("app. name", os.environ.get("GAUDIAPPNAME")),
535 ("app. version", os.environ.get("GAUDIAPPVERSION")),
536 ]:
537 self.log.info("%s: %30s : %s ", __name__, name, value or "Undefined")
538 try:
539 events = str(c["ApplicationMgr"].EvtMax)
540 except Exception:
541 events = "Undetermined"
542 self.log.info("%s: Events Specified : %s ", __name__, events)
543 self.log.info("-" * 80)
544 # Parall = gpp.Coordinator(ncpus, shared, c, self.log)
545 Parall = gpp.Coord(ncpus, c, self.log)
546 sysStart = time()
547 sc = Parall.Go()
548 self.log.info("MAIN.PY : received %s from Coordinator" % (sc))
549 if sc.isFailure():
550 return 1
551 sysTime = time() - sysStart
552 self.log.name = "Gaudi/Main.py Logger"
553 self.log.info("-" * 80)
554 self.log.info(
555 "%s: parallel system finished, time taken: %5.4fs", __name__, sysTime
556 )
557 self.log.info("-" * 80)
558 return 0
bool py_bootstrap_app_run(IInterface *i, int maxevt)
int PyHelper ROOT_VERSION_CODE()
GAUDI_API std::string format(const char *,...)
MsgStream format utility "a la sprintf(...)".
Definition MsgStream.cpp:93
void py_helper_printAlgsSequences(IInterface *app)
Helper to call printAlgsSequences from Pyhton ctypes.
The Application Manager class.
create(cls, appType, opts)
Definition __init__.py:129
setOption(self, key, value)
Definition Main.py:105
__init__(self, ptr, lib)
Definition Main.py:51
setProperty(self, name, value)
Definition Main.py:90
runParallel(self, ncpus)
Definition Main.py:523
runSerial(self, attach_debugger)
Definition Main.py:481
generateOptsOutput(self, all=False)
Definition Main.py:384
writeconfig(self, filename, all=False)
Definition Main.py:416
_writepickle(self, filename)
Definition Main.py:391
hookDebugger(self, debugger="gdb")
Definition Main.py:463
setupParallelLogging(self)
Definition Main.py:331
printconfig(self, old_format=False, all=False)
Definition Main.py:405
run(self, attach_debugger, ncpus=None)
Definition Main.py:454
generatePyOutput(self, all=False)
Definition Main.py:369
getAllOpts(explicit_defaults=False)
Definition Main.py:225
_getAllOpts_old(explicit_defaults=False)
Definition Main.py:178
parseOpt(s)
Definition Main.py:287
toOpt(value)
Definition Main.py:263
GAUDI_API IAppMgrUI * createApplicationMgr()