ATLAS Offline Software
Loading...
Searching...
No Matches
ConfigurationShelve.py
Go to the documentation of this file.
1# Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
2
3# @file: AthenaCommon/python/ConfigurationShelve.py
4# @author: Wim Lavrijsen (WLavrijsen@lbl.gov)
5# @author: Sebastien Binet <binet@cern.ch>
6
7import pickle
8import shelve
9
10"""Shelves for writing jars of configuration. Example:
11
12WRITING:
13
14 shelve = ConfigurationShelve( "myjob" )
15 jar = ConfigurationJar( 'myjar' )
16 shelve.store( jar )
17
18READING:
19 shelve = ConfigurationShelve( "myjob" )
20 jar = shelve.retrieve( 'myjar' )
21"""
22
23
24
25__version__ = '1.1.0'
26__author__ = """\
27Wim Lavrijsen (WLavrijsen@lbl.gov),
28Sebastien Binet <binet@cern.ch>"""
29
30__all__ = [ 'ConfigurationShelve', 'ConfigurationJar',
31 'saveToAscii', 'loadFromAscii',
32 'saveToPickle', 'loadFromPickle',
33 'cmpConfigs',
34 ]
35
36
38 __openShelves = {}
39
40 def __init__( self, name ):
41 try:
42 self._shelve = self.__openShelves[ name ]
43 except KeyError:
44 self._shelve = shelve.open( name, protocol = pickle.HIGHEST_PROTOCOL )
45
46 def __getitem__( self, key, refresh = True ):
47 if refresh:
48 # remove current configuration first, as much as possible, to prevent
49 # hysteresis; note that if the user keeps configurables around in the
50 # main (or any other) space, a merge will occur
51 from AthenaCommon.AlgSequence import AlgSequence
52
53 topSeq = AlgSequence()
54 topSeq.removeAll()
55
56 from AthenaCommon.AppMgr import theApp, ServiceMgr, ToolSvc, theAuditorSvc
57
58 theApp._streams.removeAll()
59 ServiceMgr.removeAll()
60 ToolSvc.removeAll()
61 theAuditorSvc.removeAll()
62
63 del topSeq, theApp, ServiceMgr, ToolSvc, theAuditorSvc
64
65 return self._shelve[ key ]
66
67 def __setitem__( self, key, value ):
68 self._shelve[ key ] = value
69
70 def store( self, jar ):
71 return self.__setitem__( jar.getName(), jar )
72
73 def retrieve( self, jarname, refresh = True ):
74 return self.__getitem__( jarname, refresh )
75
76
77
79 def __init__( self, name ):
80 self.name = name
81
82 from AthenaCommon.AppMgr import theApp, ServiceMgr
83 from AthenaCommon.JobProperties import jobproperties
84
85 from . import AlgSequence as _as
86 self.athMasterSeq = _as.AthSequencer ("AthMasterSeq")
87 self.athAlgSeq = _as.AthSequencer ("AthAlgSeq")
88 self.athOutSeq = _as.AthSequencer ("AthOutSeq")
89
90 self.AppMgr = theApp
91 self.ServiceMgr = ServiceMgr # takes care of AuditorSvc & ToolSvc
92 self.JobProperties = jobproperties
93
94 def getName( self ):
95 return self.name
96
97 def content( self ):
98 return self.__getstate__()
99
100 def __getstate__( self ):
101 d = dict()
102
103 d[ 'name' ] = self.name
104
105 d[ 'AthMasterSeq' ] = self.athMasterSeq
106 d[ 'AthAlgSeq' ] = self.athAlgSeq
107 d[ 'AthOutSeq' ] = self.athOutSeq
108 d[ 'AppMgr' ] = self.AppMgr
109 d[ 'Streams' ] = self.AppMgr._streams
110 d[ 'ServiceMgr' ] = self.ServiceMgr #takes care of {Auditor,Tool}Svc
111 d[ 'JobProperties' ] = self.JobProperties
112
113 return d
114
115 def __setstate__( self, d ):
116 # get handles to the global objects for convenience
117 self.__init__( d[ 'name' ] )
118
119 # the mere existence of 'd' has done its work through the "magic"
120 # of instances shared on name for all configurables
121
122 # now handle jobproperties
123 import AthenaCommon.JobProperties as JobProperties
124 JobProperties.jobproperties = d['JobProperties']
125
126 def __str__(self):
127 import os
128 return os.linesep.join( [ "%s" % v for v in self.content().values() ] )
129
130
131
132
133#----- storing to db
135 """Inspect the 'configured' JobOptionsSvc and existing services,
136 then dump the properties of each component into a pickle file.
137 """
138
139 from collections import defaultdict
140 jocat = defaultdict( dict )
141 jocfg = defaultdict( dict )
142
143 def _fillCfg( client, props ):
144 for p in props:
145 n, v = p
146 if hasattr( v, 'toStringProperty' ):
147 v = str( v.toStringProperty() )
148 elif hasattr( v, 'toString' ):
149 v = str( v.toString() )
150 elif type (v) is float:
151 # str(1.8e12) will give '1.8e+12' in py2
152 # and `1800000000000.0' in py3.
153 # Convert floats like this for consistency.
154 v = '%g'%v
155 else:
156 v = str( v )
157 jocfg[ client ][ n ] = v
158
159 from AthenaCommon.AppMgr import theApp
160 from AthenaCommon.AppMgr import ServiceMgr as svcMgr
161 from AthenaCommon.Configurable import Configurable as C
162
163 # tickle the Gaudi C++ side and configure the ApplicationMgr
164 theApp.setup( recursive = True )
165
166 app_props = [ (k,v.value())
167 for k,v in theApp.getHandle().properties().items() ]
168 _fillCfg( 'ApplicationMgr', app_props )
169
170 # get all services that now already exist, as they require special treatment;
171 # the appmgr handle talks directly to the ServiceManager and services() returns
172 # a copy of the services names
173 svcs = theApp.getHandle().services()
174
175 # now assume that if these services carry configuration, then they should exist
176 # on the service manager configurable
177 for svcname in svcs:
178 svc = getattr( svcMgr, svcname, None )
179 if svc:
180 _fillCfg( svcname, svc.getValuedProperties().items() )
181
182 # make sure to propagate the EventLoop properties through the josvc
183 try:
184 evLoopName = theApp.EventLoop.split('/')[-1]
185 evLoop = getattr( svcMgr, evLoopName )
186
187 props = []
188 for k,v in evLoop.properties().items():
189 if v != C.propertyNoValue:
190 props.append( (k,v) )
191 _fillCfg( evLoopName, props )
192
193 except AttributeError:
194 pass # no properties defined for EventLoop type
195
196 # get the values for all other components (these may contain duplicates of
197 # the ones above in svcs, and there may even be conflicts)
198 import AthenaPython.PyAthena as PyAthena
199 josvc = PyAthena.py_svc( 'JobOptionsSvc',iface="Gaudi::Interfaces::IOptionsSvc")
200 allProps=josvc.items() #a std::tuple<string,string>
201 for prop in allProps:
202 cn=str(prop._0).split(".")
203 v=str(prop._1)
204 client=".".join(cn[:-1])
205 n=cn[-1]
206 jocat[ client ][ n ] = v
207
208 # take care of some ancient history
209 for k in ( 'Go', 'Exit' ):
210 if k in jocfg[ 'ApplicationMgr' ]:
211 del jocfg[ 'ApplicationMgr' ][k]
212
213 # workaround for pycomps
214 pycomps = []
215 for c in C.allConfigurables.values():
216 if not isinstance( c, (PyAthena.Alg,
217 PyAthena.AlgTool,
219 PyAthena.Aud) ):
220 continue
221 # FIXME: should check if the component is still 'active'
222 # ie: is it in one of the TopAlg,SvcMgr,ToolSvc and children ?
223 try:
224 delattr( c, 'msg' )
225 except AttributeError:
226 pass
227
228 pycomps.append( c )
229
230 # write out all parts; start with the jocat, so that it can be loaded by
231 # itself only, if need be
232
233 cfg = open( cfg_fname, 'wb' ) # output pickle file
234
235 pickle.dump( jocat, cfg ) # jobopt catalogue contents
236 pickle.dump( jocfg, cfg ) # special services' properties
237 pickle.dump( pycomps, cfg ) # python components workaround
238
239 cfg.close()
240
241 return cfg_fname
242
243
244#----- loading from DB
245def loadJobOptionsCatalogue( cfg_fname ):
246 """Load properties from a pickle file, previously dumped by
247 storeConfiguration, back into the JobOptionsSvc.
248 """
249 # read jobopt catalogue dump and pycomps back in
250 cfg = open( cfg_fname, 'rb' )
251
252 jocat = pickle.load( cfg )
253 jocfg = pickle.load( cfg )
254 pycomps = pickle.load( cfg )
255
256 cfg.close()
257
258 kw = jocfg[ 'ApplicationMgr' ]
259 from AthenaCommon.AppMgr import theApp
260 theApp.JobOptionsSvcType = kw[ 'JobOptionsSvcType' ]
261 theApp.MessageSvcType = kw[ 'MessageSvcType' ]
262 theApp.getHandle( kw )
263 del jocfg[ 'ApplicationMgr' ]
264
265 # no longer want to call setup(), since there are no Configurables to
266 # setup; it would be a no-op, already, but __build_master_sequence is
267 # broken, so make sure this can't be run ...
268 def noop( self ):
269 pass
270 theApp.__class__.setup = noop
271
272 import AthenaPython.PyAthena as PyAthena
273 josvc = PyAthena.py_svc( 'JobOptionsSvc', createIf = False, iface = 'Gaudi::Interfaces::IOptionsSvc')
274
275 # restore job catalogue entries
276 import GaudiPython.Bindings as gaudi
277 for client in jocat:
278 if client == "ApplicationMgr":
279 # ApplicationMgr properties are already set
280 continue
281 for n,v in jocat[ client ].items():
282 josvc.set( client+'.'+n, v )
283
284 # restore special services properties
285 for client in jocfg:
286 svc = PyAthena.py_svc( client, createIf = False, iface='IProperty' )
287 for n,v in jocfg[ client ].items():
288 # See comment above.
289 p = gaudi.StringProperty()
290 p.setName(n)
291 p.fromString(v).ignore()
292 svc.setProperty( p )
293
294 # pycomps hack-around
295 if pycomps:
296 import AthenaPython.Configurables as _C
297 _C.PyComponents.instances = dict( (p.name, p) for p in pycomps )
298 for p in pycomps:
299 if hasattr( p, 'setup' ):
300 if callable( p.setup ):
301 p.setup()
302
303
304
305def saveToAscii(out, cfgName=None):
306 """
307 Helper function to store the current configuration to (ASCII) file
308 """
309 import os
310
311 # temporarily set the Configurable log level to make sure that the printout
312 # does not get suppressed
313 from AthenaCommon import Configurable
314 from AthenaCommon import Constants
315 llevel = Configurable.log.level
316 Configurable.log.setLevel( Constants.INFO )
317
318 if cfgName is None: cfgName = "Snapshot"
319 content = ConfigurationJar( cfgName ).content()
320
321 if type(out) is str:
322 out = open( out, "w" )
323 for v in content.values(): out.write( str(v) + os.linesep )
324
325 Configurable.log.setLevel( llevel )
326
327 return
328
329def loadFromAscii(fileName, cfgName=None):
330 """
331 Helper function to retrieve a configuration from an ASCII file.
332 """
333 raise NotImplementedError("Sorry no restoration from ASCII file (yet?)")
334
335def saveToPickle(fileName, cfgName=None):
336 """
337 Helper function to store the current configuration to a pickle file
338 """
339 if cfgName is None: cfgName = "Snapshot"
340 shelve = ConfigurationShelve( fileName )
341 jar = ConfigurationJar( cfgName )
342 shelve.store( jar )
343
344 return
345
346def loadFromPickle(fileName, cfgName=None):
347 """
348 Helper function to retrieve a configuration from a pickle file
349 """
350 if cfgName is None: cfgName = "Snapshot"
351 import os
352 fileName = os.path.expanduser(os.path.expandvars(fileName))
353 shelve = ConfigurationShelve( fileName )
354 # configuration is loaded by side-effects...
355 jar = shelve.retrieve( cfgName )
356 return jar
357
358def cmpConfigs (ref, chk, refName=None, chkName=None):
359 """
360 Helper function to compare 2 configurations.
361 @param ref the path to a pickle file where a configuration was stored
362 @param chk the path to a pickle file where a configuration was stored
363 @param refName the name of the configuration jar to load
364 @param chkName the name of the configuration jar to load
365 if `ref` and/or `chk` are joboptions (ie: their extension is '.py') a shelve
366 is automatically created on the fly
367 """
368 import os
369 _path = os.path
370 ref = _path.expanduser(_path.expandvars(ref))
371 chk = _path.expanduser(_path.expandvars(chk))
372
373 from PyUtils.Decorators import forking
374 # if we are given joboptions, create ConfigurationShelves on the fly
375 @forking
376 def _create_shelve (joboname):
377 from tempfile import NamedTemporaryFile
378 fname = os.path.basename (joboname)
379 fname = os.path.splitext(fname)[0] + '.pkl'
380 fname = NamedTemporaryFile(prefix=fname, suffix='.pkl').name
381 job = [ "from AthenaCommon.Include import include",
382 "include ('%s')" % joboname,
383 "from AthenaCommon.ConfigurationShelve import saveToPickle",
384 "saveToPickle ('%s')" % fname,
385 # exit before actually running the job
386 "raise SystemExit(0)",
387 ]
388 jobofile = NamedTemporaryFile(suffix='.py')
389 map (jobofile.writelines, [l+os.linesep for l in job])
390 jobofile.flush()
391
392 from subprocess import getstatusoutput
393 sc,out = getstatusoutput ('athena.py %s' % jobofile.name)
394
395 jobofile.close()
396 if sc==0:
397 return fname
398 return (sc, out)
399
400 if os.path.splitext(ref)[1]=='.py':
401 print ("::: creating a shelve on the fly for [%s]..."%ref)
402 ref = _create_shelve (ref)
403 if not isinstance(ref, str):
404 raise RuntimeError (
405 'could not create a shelve on the fly:\n%s'%ref[1])
406 import atexit
407 atexit.register (os.unlink, ref)
408
409 if os.path.splitext(chk)[1]=='.py':
410 print ("::: creating a shelve on the fly for [%s]..."%chk)
411 chk = _create_shelve (chk)
412 if not isinstance(chk, str):
413 raise RuntimeError (
414 'could not create a shelve on the fly:\n%s'%chk[1])
415 import atexit
416 atexit.register (os.unlink, chk)
417
418 for fname in (ref, chk):
419 if not _path.exists (fname):
420 raise RuntimeError ('file [%s] does not exist'%fname)
421
422 @forking
423 def _dict_cfg (fname, cfgName):
424 #from PyUtils.odict import OrderedDict as odict
425 jar = loadFromPickle (fname, cfgName)
426 d = list()
427 all_cfgs = dict()
428 all_cfgs.setdefault (dict)
429 def _visit_cfg (cfg):
430 name = cfg.getJobOptName()
431 values = [name]
432 all_cfgs[name] = dict()
433 props = cfg.getProperties()
434 def _get_value (cfg, k, v):
435 _no_value = cfg.propertyNoValue
436 if v == _no_value:
437 v = cfg.getDefaultProperty(k)
438 return v
439
440 from AthenaCommon.Configurable import Configurable
441 for k,v in props.items():
442 if not isinstance(v, Configurable):
443 all_cfgs[name][k] = _get_value(cfg,k,v)
444 else:
445 all_cfgs[name][k] = v.getJobOptName()
446 if v not in cfg.getAllChildren():
447 values.extend (_visit_cfg(v))
448
449 if hasattr(cfg, 'getAllChildren'):
450 for c in cfg.getAllChildren():
451 values.extend (_visit_cfg(c))
452 return values
453
454 for c in jar.athAlgSeq.getChildren():
455 d.extend (_visit_cfg(c))
456
457 for c in jar.ServiceMgr.getChildren():
458 d.extend (_visit_cfg(c))
459 return d, all_cfgs
460
461 ref_list, ref_cfgs = _dict_cfg (ref, refName)
462 chk_list, chk_cfgs = _dict_cfg (chk, chkName)
463
464 report = []
465 addNames,subNames = [],[]
466 if ref_list != chk_list:
467 report.append ("::: configurable list in 'ref' and in 'chk' differ !")
468 addNames = sorted([ n for n in chk_cfgs if not (n in ref_cfgs) ])
469 if len (addNames) > 0:
470 report.append ("::: configurables in 'chk' and not in 'ref':")
471 report.extend ([" + %s" % n for n in addNames])
472 subNames = sorted([ n for n in ref_cfgs if not (n in chk_cfgs) ])
473 if len (subNames) > 0:
474 report.append ("::: configurables in 'ref' and not in 'chk':")
475 report.extend ([" - %s" % n for n in subNames])
476
477 common_cfgs = set (
478 [n for n in ref_list if not (n in addNames or n in subNames)] +
479 [n for n in chk_list if not (n in addNames or n in subNames)]
480 )
481 for n in common_cfgs:
482 ref_cfg = ref_cfgs[n]
483 chk_cfg = chk_cfgs[n]
484 diff_props_report = []
485 for k in ref_cfg.iterkeys():
486 ref_val = str(ref_cfg.get(k, '**N/A**'))
487 chk_val = str(chk_cfg.get(k, '**N/A**'))
488
489 if ref_val != chk_val:
490 diff_props_report.append (" ref: %s = %r" % (k, ref_val))
491 diff_props_report.append (" chk: %s = %r" % (k, chk_val))
492 if len(diff_props_report)>0:
493 report.append ("::: properties differing for [%s]:"%n)
494 report.extend (diff_props_report)
495 return (ref_list, ref_cfgs), (chk_list, chk_cfgs), report
representation of job databases ==========================================
std::vector< std::string > split(const std::string &s, const std::string &t=":")
Definition hcg.cxx:177
loadFromAscii(fileName, cfgName=None)
saveToAscii(out, cfgName=None)
further saving utilities =================================================
cmpConfigs(ref, chk, refName=None, chkName=None)
loadFromPickle(fileName, cfgName=None)
saveToPickle(fileName, cfgName=None)