ATLAS Offline Software
Loading...
Searching...
No Matches
AtlCoolLib.py
Go to the documentation of this file.
1#!/usr/bin/env python
2
3# Copyright (C) 2002-2024 CERN for the benefit of the ATLAS collaboration
4# AtlCoolLib.py
5# module defining utilities for ATLAS command line tool use of COOL
6# Richard Hawkings, started 5/2/07
7"""
8Module defining utilities for ATLAS command line/python use of COOL
9"""
10
11import sys,os,getopt,time,calendar
12from PyCool import cool
13# Work around pyroot issue with long long --- see ATEAM-997.
14import ROOT
15ROOT.gInterpreter
16
17def transConn(conn):
18 """
19 Translate simple connection string (no slash) to mycool.db with given
20 instance, all others left alone
21 """
22 if ('/' not in conn):
23 return 'sqlite://X;schema=mycool.db;dbname='+conn
24 else:
25 return conn
26
27def forceOpen(conn):
28 """
29 Open COOL database with given connection, or force open if not possible
30 Return COOL database object, or None if cannot be opened
31 Database is opened in update mode
32 """
33 dbSvc=cool.DatabaseSvcFactory.databaseService()
34 conn2=transConn(conn)
35 try:
36 db=dbSvc.openDatabase(conn2,False)
37 except Exception as e:
38 print (e)
39 print ('Could not connect to',conn,'try to create it')
40 try:
41 db=dbSvc.createDatabase(conn2)
42 except Exception as e:
43 print (e)
44 print ('Could not create the database - give up')
45 return None
46 return db
47
48def readOpen(conn):
49 """
50 Open COOL database with given connection for reading
51 """
52 dbSvc=cool.DatabaseSvcFactory.databaseService()
53 conn2=transConn(conn)
54 try:
55 db=dbSvc.openDatabase(conn2,True)
56 except Exception as e:
57 print (e)
58 print ('Could not connect to',conn)
59 return None
60 return db
61
62def ensureFolder(db,folder,spec,desc,version=cool.FolderVersioning.SINGLE_VERSION):
63 """
64 Ensure folder exists, creating it if needed with given spec
65 Return COOL folder object
66 """
67 if (not db.existsFolder(folder)):
68 print ('Attempting to create',folder,'with description',desc)
69 try:
70 # Deprecated/dropped: db.createFolder(folder,spec,desc,version,True)
71 folderSpec = cool.FolderSpecification(version,spec)
72 db.createFolder(folder,folderSpec,desc,True)
73 print ('Folder created')
74 except Exception as e:
75 print (e)
76 print ('Could not create folder',folder)
77 return None
78 return db.getFolder(folder)
79
80def athenaDesc(runLumi,datatype):
81 """
82 Return the correct folder description string for Athena IOVDbSVc access
83 runLumi=True or False for timestamp
84 datatype= { AthenaAttributeList | CondAttrListCollection }
85 """
86 if (runLumi):
87 desc='<timeStamp>run-lumi</timeStamp>'
88 else:
89 desc='<timeStamp>time</timeStamp>'
90 stype=71
91 clid=0
92 tname=''
93 if (datatype=='CondAttrListCollection'):
94 clid=1238547719
95 tname='CondAttrListCollection'
96 elif (datatype=='AthenaAttributeList'):
97 clid=40774348
98 tname='AthenaAttributeList'
99 elif (datatype=='CondAttrListVec'):
100 clid=55403898
101 tname='CondAttrListVec'
102
103 desc+='<addrHeader><address_header service_type=\"'+str(stype)+'\" clid=\"'+str(clid)+'\" /></addrHeader><typeName>'+tname+'</typeName>'
104 return desc
105
106def timeVal(val):
107 "Convert argument to time in seconds, treating as a literal or date"
108 try:
109 a=int(val)
110 return a
111 except ValueError:
112 try:
113 ts=time.strptime(val+'/UTC','%Y-%m-%d:%H:%M:%S/%Z')
114 return int(calendar.timegm(ts))
115 except ValueError:
116 print ("ERROR in time specification, use e.g. 2007-05-25:14:01:00")
117 sys.exit(-1)
118
119def timeString(iovkey):
120 "Convert the IOVkey (63 bit) to a string representing timestamp"
121 if (iovkey==cool.ValidityKeyMin):
122 return "ValidityKeyMin"
123 elif (iovkey==cool.ValidityKeyMax):
124 return "ValidityKeyMax"
125 else:
126 stime=int(iovkey/1000000000)
127 return time.asctime(time.gmtime(stime))+" UTC"
128
129def indirectOpen(coolstr,readOnly=True,debug=False):
130 """Obtain a connection to the database coolstr (e.g.
131 COOLONL_INDET/OFLP200) using Oracle servers only, bypassing SQLite files
132 and simulating the action of DBReplicaSvc to choose the correct one.
133 Returns None in case a connection cannot be established.
134 debug=True produces debug printout to show servers being tried"""
135 dbSvc=cool.DatabaseSvcFactory.databaseService()
136 connstr=transConn(coolstr)
137
138 # split the name into schema and dbinstance
139 splitname=connstr.split('/')
140 forceSQLite='ATLAS_COOL_FORCESQLITE' in os.environ
141 if (debug and forceSQLite):
142 print ("ATLAS_COOL_FORCESQLITE: Force consideration of SQLite replicas")
143 if (len(splitname)!=2 or readOnly is False or forceSQLite):
144 try:
145 db=dbSvc.openDatabase(connstr,readOnly)
146 except Exception as e:
147 # if writeable connection on readonly filesystem, get
148 # 'cannot be established error' - retry readonly
149 if (not readOnly and ('attempt to write a readonly database' in e.__repr__())):
150 print ("Writeable open failed - retry in readonly mode")
151 db=dbSvc.openDatabase(connstr,True)
152 else:
153 raise
154 return db
155 if (debug): print ("Trying to establish connection for %s from Oracle" % connstr)
156 schema=splitname[0]
157 dbinst=splitname[1]
158 # list of servers to try, first one at beginning
159 serverlist=replicaList()
160 if (debug): print (serverlist)
161 # will pop candidates from end of list - so reverse it first
162 serverlist.reverse()
163 while len(serverlist)>0:
164 server=serverlist.pop()
165 # deal with frontier server - use only if FRONTIER_SERVER is set
166 if server=='ATLF':
167 if ('FRONTIER_SERVER' in os.environ):
168 connstr='frontier://ATLF/();schema=ATLAS_%s;dbname=%s' % (schema,dbinst)
169 else:
170 # skip ATLF replica if FRONTIER_SERVER not set
171 continue
172 elif server=='atlas_dd': continue
173 else:
174 connstr='oracle://%s;schema=ATLAS_%s;dbname=%s' % (server,schema,dbinst)
175 if (debug): print ("Attempting to open %s" % connstr)
176 try:
177 dbconn=dbSvc.openDatabase(connstr)
178 if (dbconn is not None and dbconn.isOpen()):
179 if (debug): print ("Established connection to %s" % server)
180 return dbconn
181 if (debug): print ("Cannot connect to %s" % server)
182 except Exception:
183 if (debug): print ("Exception connecting to %s" % server)
184 # all replicas tried - give up
185 print ("All available replicas tried - giving up")
186 return None
187
189 """Return list of Oracle database server names to try, mimicing action of
190 Athena DBReplicaSvc"""
191 configfile=pathResolver('dbreplica.config')
192 if configfile is None:
193 print ("Cannot find dbreplica.config")
194 return None
195 hostname=getHostname()
196 best=0
197 serverlist=[]
198 for line in configfile.readlines():
199 epos=line.find('=')
200 if (epos>0 and line[0]!="#"):
201 domains=line[0:epos].split()
202 for dom in domains:
203 if ((hostname[-len(dom):]==dom and len(dom)>best) or (best==0 and dom=='default')):
204 best=len(dom)
205 serverlist=line[epos+1:].split()
206 configfile.close()
207 if (len(serverlist)==0):
208 print ("No suitable servers found")
209 return None
210 return serverlist
211
212def pathResolver(leaf,retFile=True):
213 """Find the file leaf using the DATAPATH variable to look for it
214 Return a read-only file object, or None if not found.
215 If retFile is false, just return the pathname.
216 Current directory takes precendence if file is there"""
217 try:
218 paths=os.environ['DATAPATH'].split(':')
219 except Exception:
220 # DATAPATH not set - just use current directory
221 paths=[]
222 paths=['.']+paths
223 for path in paths:
224 try:
225 os.stat(path+'/'+leaf)
226 if (retFile):
227 return open(path+'/'+leaf,'r')
228 else:
229 return path+'/'+leaf
230 except OSError:
231 pass
232 return None
233
235 "Get the hostname including domain name if possible"
236 if ('ATLAS_CONDDB' in os.environ):
237 name=os.environ['ATLAS_CONDDB']
238 else:
239 try:
240 name=os.environ['HOSTNAME']
241 except Exception:
242 # try socket lib (for Mac) as HOSTNAME does not contain os.environ
243 import socket
244 try:
245 name=socket.getfqdn()
246 except Exception:
247 name='unknown'
248 # check name has a . (and doesn't end with .local, as macs default to),
249 # otherwise try command hostname --fqdn
250 if (name.find('.')>=0 and name[-6:]!=".local"):
251 return name
252 else:
253 if (name[-6:]==".local"):
254 print ("WARNING. Hostname is ",name, " which does not define a domain. Consider setting $ATLAS_CONDDB to avoid this")
255 return "unknown"
256
257 import subprocess
258 try:
259 name=subprocess.check_output('hostname --fqdn').decode('utf-8')
260 except Exception:
261 name='unknown'
262
263 #Calling hostname wrong will fill 'name' with error message
264 if (name.find('illegal')>-1 or name.find('unrecognized')>-1):
265 print ("WARNING. hostname --fqdn returned the following:",name)
266 print ("Consider setting $ATLAS_CONDDB to avoid this.")
267 return "unknown"
268 return name
269
270
271# test code
272def tests():
273 print ('AtlCoolLib tests')
274 db=forceOpen("TESTCOOL")
275 if (db is None):
276 print ('Could not create test database')
277 sys.exit(1)
278 spec=cool.RecordSpecification()
279 spec.extend('testString',cool.StorageType.String255)
280 folder=ensureFolder(db,'/TEST/TEST1',spec,athenaDesc(True,'CondAttrListCollection'),cool.FolderVersioning.MULTI_VERSION)
281 if (folder is None):
282 print ('Could not create test folder')
283 sys.exit(1)
284 print ('All done')
285 return
286
288 """
289 Class coolTool implements a base for python command-line tools to access
290 and set data in COOL folders
291 Incomplete baseclass implementation - clients must implement the following:
292 - setup(self,args) - set additional arguments
293 - usage(self) - print usage
294 - execute(self) - execute command
295 - procopts(self,opts) - (optional) - process additional command switches
296 """
297 def __init__(self,name,readonly,minarg,maxarg,longopts):
298 """
299 Initialise class and process command line options and switches
300 """
301 self.name=name
302 self.readonly=readonly
303 self.runLumi=True
304 self.runmin=0
305 self.runmax=(1 << 31)-1
306 self.lumimin=0
307 self.lumimax=(1 << 32)-2
308 self.tsmin=cool.ValidityKeyMin
309 self.tsmax=cool.ValidityKeyMax
310 self.since=cool.ValidityKeyMin
311 self.until=cool.ValidityKeyMax
312 self.debug=False
313 # get and process options - note only use long options format
314 try:
315 fullopts=longopts+['r=','rs=','ru=','l=','ls=','lu=','ts=','tu=','debug']
316 opts,args=getopt.getopt(sys.argv[1:],'',fullopts)
317 except getopt.GetoptError as e:
318 print (e)
319 self.usage()
320 sys.exit(1)
321 if len(args)<minarg or len(args)>maxarg:
322 print (name,'takes between',minarg,'and',maxarg,'non-optional parameters')
323 self.usage()
324 sys.exit(1)
325 # set standard parameters
326 self.conn=transConn(str(args[0]))
327 # allow derived class to set parameters
328 self.setup(args[1:])
329 self._procopts(opts)
330 self.procopts(opts)
331 # open database connection and execute command
332 if self.readonly:
333 self.db=indirectOpen(self.conn,debug=self.debug)
334 else:
335 self.db=forceOpen(self.conn)
336 self.execute()
337
338 def _usage1(self):
339 # base implementation of usage - first part
340 print ('Usage:',self.name,' {<options>} dbname ',)
341
342 def _usage2(self):
343 # base implementation of usage - second part
344 print ('Options to specify IOV (default valid for all intervals)')
345 print ('--rs=<first run>')
346 print ('--ru=<last run> (inclusive)')
347 print ('--r=<run> (only single run)')
348 print ('--ls=<lumi block since>')
349 print ('--l=<lumi block> (only single LB)')
350 print ('--lu=<lumi block until> (inclusive)')
351 print ('--ts=<initial timestamp> (in seconds)')
352 print ('--tu=<final timestamp> (in seconds)')
353 print ('--debug: Enable debugging information')
354
355 def _procopts(self,opts):
356 # process the standard parameters
357 for o,a in opts:
358 if (o=='--rs'): self.runmin=int(a)
359 if (o=='--ru'): self.runmax=int(a)
360 if (o=='--r'):
361 self.runmin=int(a)
362 self.runmax=int(a)
363 if (o=='--ls'): self.lumimin=int(a)
364 if (o=='--lu'): self.lumimax=int(a)
365 if (o=='--l'):
366 self.lumimin=int(a)
367 self.lumimax=int(a)
368 if (o=='--ts'):
369 self.tsmin=timeVal(a)*1000000000
370 self.runLumi=False
371 if (o=='--tu'):
372 self.tsmax=timeVal(a)*1000000000
373 self.runLumi=False
374 if (o=='--debug'): self.debug=True
375 # now set the real interval of validity
376 if (self.runLumi):
377 print ('>== Data valid for run,LB [',self.runmin,',',self.lumimin,'] to [',self.runmax,',',self.lumimax,']')
378 self.since=(self.runmin << 32)+self.lumimin
379 self.until=(self.runmax << 32)+self.lumimax+1
380 else:
381 self.since=self.tsmin
382 self.until=self.tsmax
383 print ('>== Data valid for timestamps (sec) from ',self.since/1000000000,'(',timeString(self.since),') to ',self.until/1000000000,'(',timeString(self.until),')')
384 # check IOV is OK
385 if (self.until<self.since):
386 print ('ERROR in IOV definition: until<since')
387 sys.exit(1)
388
389 def procopts(self,opts):
390 # default implementation of procopts - do nothing
391 pass
392
393
394class RangeList:
395 """Hold a list of IOVs (start/end pairs) which are good, allowing parts
396 of list to be vetoed. start/end interpreted in COOL convention"""
397 def __init__(self,start,end):
398 "Initalise RangeList with given start and end (end=1 after last valid)"
399 self._starts=[start]
400 self._ends=[end]
401
402 def vetoRange(self,start,end):
403 "Veto part of the original range, splitting it if needed"
404 if (start>=end): return
405 if (len(self._starts)==0): return
406 ix=0
407 while (ix<len(self._starts) and end>self._starts[ix]):
408 if (end>=self._starts[ix]):
409 if (start<=self._starts[ix] and end>=self._ends[ix]):
410 # remove whole interval
411 self._remove(ix)
412 ix-=1
413 elif (start<=self._starts[ix] and end<self._ends[ix]):
414 # remove front of stored interval
415 self._starts[ix]=end
416 elif (start<=self._ends[ix] and end>=self._ends[ix]):
417 # remove back of stored interval
418 self._ends[ix]=start
419 elif (start>self._starts[ix] and end<self._ends[ix]):
420 # have to split the stored interval
421 oldend=self._ends[ix]
422 self._ends[ix]=start
423 self._insert(ix+1,end,oldend)
424 ix+=1
425 ix+=1
426
427 def getAllowedRanges(self,start,end):
428 """Return a list of tuples giving the allowed (start,end) within the
429 specified (start,end)"""
430 result=[]
431 for ix in range(0,len(self._starts)):
432 if (self._ends[ix]>start and self._starts[ix]<end):
433 result+=[(max(self._starts[ix],start),min(self._ends[ix],end))]
434 return result
435
436 def __str__(self):
437 "Print representation of range as list of [x,y] allowed values"
438 rep=''
439 for i in range(0,len(self._starts)):
440 rep+='[%i,%i] ' % (self._starts[i],self._ends[i])
441 return rep
442
443 def _remove(self,idx):
444 "Remove the entry at idx"
445 self._starts[idx:]=self._starts[1+idx:]
446 self._ends[idx:]=self._ends[1+idx:]
447
448 def _insert(self,idx,start,end):
449 "Put a new entry at idx, moving others to make space"
450 self._starts.insert(idx,start)
451 self._ends.insert(idx,end)
452
454 "Translation between timestamps and RunLB IOVs using /TRIGGER/LUMI/LBLB"
455 # contribution from Jahred Adelman
456 def __init__(self,dbconn,iovstart,iovend):
457 "Initialise and cache using the given DB connection, for RLB start/end"
458 # dbconn must correspond to COOLONL_TRIGGER/COMP200
459 self.StartTime = -1
460 self.EndTime = -1
461 self.since=iovstart
462 self.until=iovend
463 self.TSBeginMap = []
464 self.TSEndMap = []
465 self.RLMap = []
466 self.readdb = dbconn
467
468 lblbname='/TRIGGER/LUMI/LBLB'
469 try:
470 readfolder=self.readdb.getFolder(lblbname)
471 except Exception as e:
472 print (e)
473 print ("Could not access folder %s " % lblbname)
474 raise RuntimeError ("TimeStampToRLB initialisation error")
475 # First try to read timestamp info
476 isFirst=True
477 try:
478 readobjs=readfolder.browseObjects(self.since,self.until,cool.ChannelSelection.all())
479 while readobjs.goToNext():
480 readobj=readobjs.currentRef()
481 payload=readobj.payload()
482 if (isFirst is True):
483 isFirst=False
484 self.StartTime=payload['StartTime']
485 else:
486 self.EndTime = payload['EndTime']
487 except Exception as e:
488 print (e)
489 print ("Problem reading data from folder %s" % lblbname)
490 raise RuntimeError ("TimeStampToRLB: initialisation error")
491 if (self.StartTime==-1):
492 raise RuntimeError ("TimeStampToRLB: no data for given runs")
493
494 # Now try to read the LBTIME folder to translate timestamps into run/lumi blocks
495 lbtimename='/TRIGGER/LUMI/LBTIME'
496 try:
497 readfolder=self.readdb.getFolder(lbtimename)
498 except Exception as e:
499 print (e)
500 print ("Problem accessing folder %s" % lbtimename)
501 raise RuntimeError ("TimeStampToRLB: Initialisation error")
502 try:
503 readobjs=readfolder.browseObjects(self.StartTime, self.EndTime, cool.ChannelSelection.all())
504 while readobjs.goToNext():
505 readobj=readobjs.currentRef()
506 payload=readobj.payload()
507 TimeStampStart = readobj.since()
508 TimeStampEnd = readobj.until()
509 iov=(payload['Run'] << 32)+payload['LumiBlock']
510 self.TSBeginMap+=[TimeStampStart]
511 self.TSEndMap+=[TimeStampEnd]
512 self.RLMap+=[iov]
513 except Exception as e:
514 print (e)
515 print ("Problem reading from folder %s" % lbtimename)
516 raise RuntimeError ("TimeStampToRLB: Time data access error")
517 print ("TimeStampToRLB initialised with %i entries in map" % len(self.RLMap))
518
519 def getRLB(self,timestamp,StartType=True):
520 """Lookup a timestamp value. If it is outside a run, round up to next
521run (StartType=True) or down to previous (StartType=False)"""
522
523 # New version. Let's take advantage of the fact that the DB entries should be time-ordered
524 if (StartType):
525 for TSbegin, RL in zip(self.TSBeginMap, self.RLMap):
526 if (timestamp <= TSbegin):
527 return RL
528 # Timestamp above cached endtime - return endtime
529 return self.until
530
531 else:
532 for TSend, RL in reversed(zip(self.TSEndMap, self.RLMap)):
533 if (timestamp >= TSend):
534 return RL
535 # Timestamp below cached starttime - return starttime
536 return self.since
537
538
539
540# main code - run test of library functions
541if __name__=='__main__':
542 tests()
543 print ('All OK')
544
#define min(a, b)
Definition cfImp.cxx:40
#define max(a, b)
Definition cfImp.cxx:41
_insert(self, idx, start, end)
__init__(self, start, end)
getAllowedRanges(self, start, end)
vetoRange(self, start, end)
getRLB(self, timestamp, StartType=True)
__init__(self, dbconn, iovstart, iovend)
__init__(self, name, readonly, minarg, maxarg, longopts)
std::vector< std::string > split(const std::string &s, const std::string &t=":")
Definition hcg.cxx:177
athenaDesc(runLumi, datatype)
Definition AtlCoolLib.py:80
indirectOpen(coolstr, readOnly=True, debug=False)
pathResolver(leaf, retFile=True)
ensureFolder(db, folder, spec, desc, version=cool.FolderVersioning.SINGLE_VERSION)
Definition AtlCoolLib.py:62