The Gaudi Framework  master (bdbe7744)
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
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:
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:
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, f"py_bootstrap_{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, f"py_bootstrap_fsm_{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 '"{}"'.format(value.replace('"', '\\"'))
278 elif isinstance(value, dict):
279 return "{{{}}}".format(
280 ", ".join(f"{toOpt(k)}: {toOpt(v)}" for k, v in value.items())
281 )
282 elif hasattr(value, "__iter__"):
283 return "[{}]".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
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(f"gaudirun-{datetime}.log", "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(f"{key} = {toOpt(parseOpt(opts[key]))};" for key in keys)
389
390 def _writepickle(self, filename):
391 # --- Lets take the first file input file as the name of the pickle file
392 import pickle
393
394 output = open(filename, "wb")
395 # Dump only the the configurables that make sense to dump (not User ones)
396 from GaudiKernel.Proxy.Configurable import getNeededConfigurables
397
398 to_dump = {}
399 for n in getNeededConfigurables():
400 to_dump[n] = Configuration.allConfigurables[n]
401 pickle.dump(to_dump, output, -1)
402 output.close()
403
404 def printconfig(self, old_format=False, all=False):
405 msg = "Dumping all configurables and properties"
406 if not all:
407 msg += " (different from default)"
408 log.info(msg)
409 if old_format:
410 print(self.generateOptsOutput(all))
411 else:
412 print(self.generatePyOutput(all))
413 sys.stdout.flush()
414
415 def writeconfig(self, filename, all=False):
416 import json
417
418 write = {
419 ".pkl": lambda filename, all: self._writepickle(filename),
420 ".py": lambda filename, all: open(filename, "w").write(
421 self.generatePyOutput(all) + "\n"
422 ),
423 ".opts": lambda filename, all: open(filename, "w").write(
424 self.generateOptsOutput(all) + "\n"
425 ),
426 ".json": lambda filename, all: json.dump(
427 getAllOpts(all), open(filename, "w"), indent=2, sort_keys=True
428 ),
429 }
430 try:
431 import yaml
432
433 write[".yaml"] = lambda filename, all: yaml.safe_dump(
434 getAllOpts(all), open(filename, "w")
435 )
436 write[".yml"] = write[".yaml"]
437 except ImportError:
438 pass # yaml support is optional
439
440 from os.path import splitext
441
442 ext = splitext(filename)[1]
443 if ext in write:
444 write[ext](filename, all)
445 else:
446 log.error(
447 "Unknown file type '%s'. Must be any of %r.", ext, sorted(write.keys())
448 )
449 sys.exit(1)
450
451 # Instantiate and run the application.
452 # Depending on the number of CPUs (ncpus) specified, it start
453 def run(self, attach_debugger, ncpus=None):
454 if not ncpus:
455 # Standard sequential mode
456 result = self.runSerial(attach_debugger)
457 else:
458 # Otherwise, run with the specified number of cpus
459 result = self.runParallel(ncpus)
460 return result
461
462 def hookDebugger(self):
463 """Hook gdb to the current session. This is done by forking
464 the current process and replacing the parent with gdb, while
465 the child continues to run the program.
466 """
467
468 child_pid = os.fork()
469
470 if child_pid == 0:
471 # CHILD PROCESS - runs the actual program
472 return
473 else:
474 # PARENT PROCESS - becomes GDB
475
476 # Replace parent process with GDB
477 args = [arg for arg in sys.argv if arg != "--gdb"]
478 os.execvp(
479 "gdb",
480 [
481 "gdb",
482 "-q",
483 "-p",
484 str(child_pid),
485 "-ex",
486 f"set args {' '.join(args)}",
487 ],
488 )
489
490 def runSerial(self, attach_debugger):
491 try:
492 from GaudiKernel.Proxy.Configurable import expandvars
493 except ImportError:
494 # pass-through implementation if expandvars is not defined (AthenaCommon)
495 def expandvars(data):
496 return data
497
498 from GaudiKernel.Proxy.Configurable import Configurable
499
500 self.log.debug("runSerial: apply options")
501 conf_dict = expandvars(getAllOpts())
502 conf_dict["ApplicationMgr.JobOptionsType"] = '"NONE"'
503
504 if self.printsequence:
505 conf_dict["ApplicationMgr.PrintAlgsSequence"] = "true"
506
507 if hasattr(Configurable, "_configurationLocked"):
508 Configurable._configurationLocked = True
509
510 if attach_debugger:
511 self.hookDebugger()
512
513 self.log.debug("-" * 80)
514 self.log.debug("%s: running in serial mode", __name__)
515 self.log.debug("-" * 80)
516 sysStart = time()
517
518 import Gaudi
519
520 app = Gaudi.Application.create(self.application, conf_dict)
521 retcode = app.run()
522
523 sysTime = time() - sysStart
524 self.log.debug("-" * 80)
525 self.log.debug(
526 "%s: serial system finished, time taken: %5.4fs", __name__, sysTime
527 )
528 self.log.debug("-" * 80)
529
530 return retcode
531
532 def runParallel(self, ncpus):
534 import GaudiMP.GMPBase as gpp
535 from Gaudi.Configuration import Configurable
536
537 c = Configurable.allConfigurables
538 self.log.info("-" * 80)
539 self.log.info("%s: Parallel Mode : %i ", __name__, ncpus)
540 for name, value in [
541 ("platform", " ".join(os.uname())),
542 ("config", os.environ.get("BINARY_TAG") or os.environ.get("CMTCONFIG")),
543 ("app. name", os.environ.get("GAUDIAPPNAME")),
544 ("app. version", os.environ.get("GAUDIAPPVERSION")),
545 ]:
546 self.log.info("%s: %30s : %s ", __name__, name, value or "Undefined")
547 try:
548 events = str(c["ApplicationMgr"].EvtMax)
549 except Exception:
550 events = "Undetermined"
551 self.log.info("%s: Events Specified : %s ", __name__, events)
552 self.log.info("-" * 80)
553 # Parall = gpp.Coordinator(ncpus, shared, c, self.log)
554 Parall = gpp.Coord(ncpus, c, self.log)
555 sysStart = time()
556 sc = Parall.Go()
557 self.log.info(f"MAIN.PY : received {sc} from Coordinator")
558 if sc.isFailure():
559 return 1
560 sysTime = time() - sysStart
561 self.log.name = "Gaudi/Main.py Logger"
562 self.log.info("-" * 80)
563 self.log.info(
564 "%s: parallel system finished, time taken: %5.4fs", __name__, sysTime
565 )
566 self.log.info("-" * 80)
567 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:127
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:532
runSerial(self, attach_debugger)
Definition Main.py:490
generateOptsOutput(self, all=False)
Definition Main.py:385
writeconfig(self, filename, all=False)
Definition Main.py:415
_writepickle(self, filename)
Definition Main.py:390
setupParallelLogging(self)
Definition Main.py:332
printconfig(self, old_format=False, all=False)
Definition Main.py:404
run(self, attach_debugger, ncpus=None)
Definition Main.py:453
generatePyOutput(self, all=False)
Definition Main.py:370
toOpt(value)
Definition Main.py:264
getAllOpts(explicit_defaults=False)
Definition Main.py:226
parseOpt(s)
Definition Main.py:288
_getAllOpts_old(explicit_defaults=False)
Definition Main.py:178
GAUDI_API IAppMgrUI * createApplicationMgr()