The Gaudi Framework  master (adcf1ca6)
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 hasattr(v, "__opt_value__"):
216 v = v.__opt_value__()
217 elif isinstance(v, str):
218 v = repr(v)
219 else:
220 v = str(v)
221 old_opts[".".join((n, p))] = v
222
223 return old_opts
224
225
226def getAllOpts(explicit_defaults=False):
227 """
228 Return all options from the old and new configuration system as a dictionary.
229
230 If explicit_defaults is true, include default values of unset properties in the dictionary.
231 """
232 import GaudiConfig2
233
234 # We need to run normal (without defaults) collection first (regardless of explicit_defaults)
235 # as the conflicts detection makes sense only for explicitly set options
236 old_opts = _getAllOpts_old(False)
237 opts = GaudiConfig2.all_options(False)
238
239 conflicts = [n for n in set(opts).intersection(old_opts) if opts[n] != old_opts[n]]
240 if conflicts:
241 conflicts.sort()
242 log.error("Some properties are set in old and new style configuration")
243 log.warning("name: old -> new")
244 for n in conflicts:
245 log.warning("%s: %s -> %s", n, old_opts[n], opts[n])
246 sys.exit(10)
247
248 opts.update(old_opts)
249
250 if explicit_defaults:
251 # If we are asked to print also the defaults, we collect everything
252 # and blindly merge to make sure we have the full set of configurables
253 all_opts = _getAllOpts_old(True)
254 all_opts.update(GaudiConfig2.all_options(True))
255 # the we override the dictionary with the explicitly set options
256 # (that leaves the defaults untouched and the set options with the
257 # correct value)
258 all_opts.update(opts)
259 opts = all_opts
260
261 return opts
262
263
264def toOpt(value):
265 """
266 Helper to convert values to old .opts format.
267
268 >>> print(toOpt('some "text"'))
269 "some \\"text\\""
270 >>> print(toOpt('first\\nsecond'))
271 "first
272 second"
273 >>> print(toOpt({'a': [1, 2, '3']}))
274 {"a": [1, 2, "3"]}
275 """
276 if isinstance(value, str):
277 return '"{0}"'.format(value.replace('"', '\\"'))
278 elif isinstance(value, dict):
279 return "{{{0}}}".format(
280 ", ".join("{0}: {1}".format(toOpt(k), toOpt(v)) for k, v in value.items())
281 )
282 elif hasattr(value, "__iter__"):
283 return "[{0}]".format(", ".join(map(toOpt, value)))
284 else:
285 return repr(value)
286
287
288def parseOpt(s):
289 """
290 Helper to parse option strings to Python values.
291
292 Ideally it should just be "eval", but the string parser of Gaudi
293 is different from the Python one, so we get string options that
294 cannot be just evaluated.
295
296 >>> print(parseOpt('123'))
297 123
298 >>> print(parseOpt('"some\\n\\\\"text\\\\""'))
299 some
300 "text"
301 >>> print(parseOpt(''))
302 <BLANKLINE>
303
304 (see gaudi/Gaudi#78)
305 """
306 import re
307
308 quoted_string = re.compile(r'^"(.*)"$', re.DOTALL)
309 # FIXME: this is needed because we cannot use repr for strings
310 # (see gaudi/Gaudi#78)
311 if not s: # pass through empty strings
312 return s
313 m = quoted_string.match(s)
314 if m:
315 return m.group(1).replace('\\"', '"')
316 return eval(s)
317
318
319class gaudimain(object):
320 def __init__(self):
321 from Configurables import ApplicationMgr
322
323 appMgr = ApplicationMgr()
324 if os.environ.get("GAUDIAPPNAME"):
325 appMgr.AppName = str(os.environ["GAUDIAPPNAME"])
326 if os.environ.get("GAUDIAPPVERSION"):
327 appMgr.AppVersion = str(os.environ["GAUDIAPPVERSION"])
328 self.log = logging.getLogger(__name__)
329 self.printsequence = False
330 self.application = "Gaudi::Application"
331
333 # ---------------------------------------------------
334 # set up Logging
335 # ----------------
336 # from multiprocessing import enableLogging, getLogger
337 import multiprocessing
338
339 # preliminaries for handlers/output files, etc.
340 from time import ctime
341
342 datetime = ctime()
343 datetime = datetime.replace(" ", "_")
344 outfile = open("gaudirun-%s.log" % (datetime), "w")
345 # two handlers, one for a log file, one for terminal
346 streamhandler = logging.StreamHandler(stream=outfile)
347 console = logging.StreamHandler()
348 # create formatter : the params in parentheses are variable names available via logging
349 formatter = logging.Formatter(
350 "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
351 )
352 # add formatter to Handler
353 streamhandler.setFormatter(formatter)
354 console.setFormatter(formatter)
355 # now, configure the logger
356 # enableLogging( level=0 )
357 # self.log = getLogger()
358 self.log = multiprocessing.log_to_stderr()
359 self.log.setLevel(logging.INFO)
360 self.log.name = "Gaudi/Main.py Logger"
361 self.log.handlers = []
362 # add handlers to logger : one for output to a file, one for console output
363 self.log.addHandler(streamhandler)
364 self.log.addHandler(console)
365 self.log.removeHandler(console)
366 # set level!!
367 self.log.setLevel = logging.INFO
368 # ---------------------------------------------------
369
370 def generatePyOutput(self, all=False):
371 import re
372 from collections import defaultdict
373 from pprint import pformat
374
375 optDict = defaultdict(dict)
376 allOpts = getAllOpts(all)
377 for key in allOpts:
378 c, p = key.rsplit(".", 1)
379 optDict[c][p] = parseOpt(allOpts[key])
380 formatted = pformat(dict(optDict))
381 # undo splitting of strings on multiple lines
382
383 return re.sub(r'"\n +"', "", formatted, flags=re.MULTILINE)
384
385 def generateOptsOutput(self, all=False):
386 opts = getAllOpts(all)
387 keys = sorted(opts)
388 return "\n".join(
389 "{} = {};".format(key, toOpt(parseOpt(opts[key]))) for key in keys
390 )
391
392 def _writepickle(self, filename):
393 # --- Lets take the first file input file as the name of the pickle file
394 import pickle
395
396 output = open(filename, "wb")
397 # Dump only the the configurables that make sense to dump (not User ones)
398 from GaudiKernel.Proxy.Configurable import getNeededConfigurables
399
400 to_dump = {}
401 for n in getNeededConfigurables():
402 to_dump[n] = Configuration.allConfigurables[n]
403 pickle.dump(to_dump, output, -1)
404 output.close()
405
406 def printconfig(self, old_format=False, all=False):
407 msg = "Dumping all configurables and properties"
408 if not all:
409 msg += " (different from default)"
410 log.info(msg)
411 if old_format:
412 print(self.generateOptsOutput(all))
413 else:
414 print(self.generatePyOutput(all))
415 sys.stdout.flush()
416
417 def writeconfig(self, filename, all=False):
418 import json
419
420 write = {
421 ".pkl": lambda filename, all: self._writepickle(filename),
422 ".py": lambda filename, all: open(filename, "w").write(
423 self.generatePyOutput(all) + "\n"
424 ),
425 ".opts": lambda filename, all: open(filename, "w").write(
426 self.generateOptsOutput(all) + "\n"
427 ),
428 ".json": lambda filename, all: json.dump(
429 getAllOpts(all), open(filename, "w"), indent=2, sort_keys=True
430 ),
431 }
432 try:
433 import yaml
434
435 write[".yaml"] = lambda filename, all: yaml.safe_dump(
436 getAllOpts(all), open(filename, "w")
437 )
438 write[".yml"] = write[".yaml"]
439 except ImportError:
440 pass # yaml support is optional
441
442 from os.path import splitext
443
444 ext = splitext(filename)[1]
445 if ext in write:
446 write[ext](filename, all)
447 else:
448 log.error(
449 "Unknown file type '%s'. Must be any of %r.", ext, sorted(write.keys())
450 )
451 sys.exit(1)
452
453 # Instantiate and run the application.
454 # Depending on the number of CPUs (ncpus) specified, it start
455 def run(self, attach_debugger, ncpus=None):
456 if not ncpus:
457 # Standard sequential mode
458 result = self.runSerial(attach_debugger)
459 else:
460 # Otherwise, run with the specified number of cpus
461 result = self.runParallel(ncpus)
462 return result
463
464 def hookDebugger(self):
465 """Hook gdb to the current session. This is done by forking
466 the current process and replacing the parent with gdb, while
467 the child continues to run the program.
468 """
469
470 child_pid = os.fork()
471
472 if child_pid == 0:
473 # CHILD PROCESS - runs the actual program
474 return
475 else:
476 # PARENT PROCESS - becomes GDB
477
478 # Replace parent process with GDB
479 args = [arg for arg in sys.argv if arg != "--gdb"]
480 os.execvp(
481 "gdb",
482 [
483 "gdb",
484 "-q",
485 "-p",
486 str(child_pid),
487 "-ex",
488 f"set args {' '.join(args)}",
489 ],
490 )
491
492 def runSerial(self, attach_debugger):
493 try:
494 from GaudiKernel.Proxy.Configurable import expandvars
495 except ImportError:
496 # pass-through implementation if expandvars is not defined (AthenaCommon)
497 def expandvars(data):
498 return data
499
500 from GaudiKernel.Proxy.Configurable import Configurable
501
502 self.log.debug("runSerial: apply options")
503 conf_dict = expandvars(getAllOpts())
504 conf_dict["ApplicationMgr.JobOptionsType"] = '"NONE"'
505
506 if self.printsequence:
507 conf_dict["ApplicationMgr.PrintAlgsSequence"] = "true"
508
509 if hasattr(Configurable, "_configurationLocked"):
510 Configurable._configurationLocked = True
511
512 if attach_debugger:
513 self.hookDebugger()
514
515 self.log.debug("-" * 80)
516 self.log.debug("%s: running in serial mode", __name__)
517 self.log.debug("-" * 80)
518 sysStart = time()
519
520 import Gaudi
521
522 app = Gaudi.Application.create(self.application, conf_dict)
523 retcode = app.run()
524
525 sysTime = time() - sysStart
526 self.log.debug("-" * 80)
527 self.log.debug(
528 "%s: serial system finished, time taken: %5.4fs", __name__, sysTime
529 )
530 self.log.debug("-" * 80)
531
532 return retcode
533
534 def runParallel(self, ncpus):
536 import GaudiMP.GMPBase as gpp
537 from Gaudi.Configuration import Configurable
538
539 c = Configurable.allConfigurables
540 self.log.info("-" * 80)
541 self.log.info("%s: Parallel Mode : %i ", __name__, ncpus)
542 for name, value in [
543 ("platform", " ".join(os.uname())),
544 ("config", os.environ.get("BINARY_TAG") or os.environ.get("CMTCONFIG")),
545 ("app. name", os.environ.get("GAUDIAPPNAME")),
546 ("app. version", os.environ.get("GAUDIAPPVERSION")),
547 ]:
548 self.log.info("%s: %30s : %s ", __name__, name, value or "Undefined")
549 try:
550 events = str(c["ApplicationMgr"].EvtMax)
551 except Exception:
552 events = "Undetermined"
553 self.log.info("%s: Events Specified : %s ", __name__, events)
554 self.log.info("-" * 80)
555 # Parall = gpp.Coordinator(ncpus, shared, c, self.log)
556 Parall = gpp.Coord(ncpus, c, self.log)
557 sysStart = time()
558 sc = Parall.Go()
559 self.log.info("MAIN.PY : received %s from Coordinator" % (sc))
560 if sc.isFailure():
561 return 1
562 sysTime = time() - sysStart
563 self.log.name = "Gaudi/Main.py Logger"
564 self.log.info("-" * 80)
565 self.log.info(
566 "%s: parallel system finished, time taken: %5.4fs", __name__, sysTime
567 )
568 self.log.info("-" * 80)
569 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:534
runSerial(self, attach_debugger)
Definition Main.py:492
generateOptsOutput(self, all=False)
Definition Main.py:385
writeconfig(self, filename, all=False)
Definition Main.py:417
_writepickle(self, filename)
Definition Main.py:392
setupParallelLogging(self)
Definition Main.py:332
printconfig(self, old_format=False, all=False)
Definition Main.py:406
run(self, attach_debugger, ncpus=None)
Definition Main.py:455
generatePyOutput(self, all=False)
Definition Main.py:370
getAllOpts(explicit_defaults=False)
Definition Main.py:226
_getAllOpts_old(explicit_defaults=False)
Definition Main.py:178
parseOpt(s)
Definition Main.py:288
toOpt(value)
Definition Main.py:264
GAUDI_API IAppMgrUI * createApplicationMgr()