7 @file CostMetadataUtil.py
8 @brief Helper functions to create cost metadata json file based on input ntuple
9 and histogram under/overflows
11 import xml.etree.ElementTree
as ET
15 from AthenaCommon.Logging
import logging
16 log = logging.getLogger(
'CostAnalysisPostProcessing')
19 def saveMetadata(inputFile, argsMetadata={}, processingWarnings=[], doTRPDetails=False, loglevel=3, maxRanges=5):
20 ''' @brief Save metadata from ntuple to json file
26 metatree = inputFile.Get(
"metadata")
33 metadata.append({
'runNumber' : metatree.runNumber})
34 metadata.append({
'Details' : argsMetadata[
"userDetails"]})
35 metadata.append({
'JIRA' : argsMetadata[
"jira"]})
36 metadata.append({
'AMITag' : argsMetadata[
"amiTag"]})
37 if "ProcessedRanges" in metadata:
38 metadata.append({
'ProcessedRanges' :
str(metatree.ProcessedRanges)})
40 if argsMetadata[
"amiTag"]:
45 if metatree.hostname
and argsMetadata[
"readOKSDetails"]:
46 metadata.append({
'OKS configuration' :
addOKSDetails(
str(metatree.hostname), metatree.runNumber, argsMetadata[
"partition"])})
47 elif metatree.hostname:
48 metadata.append({
'Hostnames' :
str(metatree.hostname)})
50 metadata.append({
'AtlasCostProcessingProject' :
str(metatree.AtlasProject)})
51 metadata.append({
'AtlasCostProcessingVersion' :
str(metatree.AtlasVersion)})
53 metadata.append({
'ChainMonitor' : metatree.ChainMonitor})
54 metadata.append({
'AlgorithmMonitor' : metatree.AlgorithmMonitor})
55 metadata.append({
'AlgorithmClassMonitor' : metatree.AlgorithmClassMonitor})
56 metadata.append({
'ROSMonitor' : metatree.ROSMonitor})
57 metadata.append({
'GlobalsMonitor' : metatree.GlobalsMonitor})
58 metadata.append({
'ThreadMonitor' : metatree.ThreadMonitor})
60 metadata.append({
'AdditionalHashMap' :
str(metatree.AdditionalHashMap)})
61 metadata.append({
'DoEBWeighting' : metatree.DoEBWeighting})
62 metadata.append({
'BaseEventWeight' : metatree.BaseEventWeight})
66 if metatree.runNumber >= 452028:
67 dtItem =
"L1_eEM26M--enabled"
69 dtItem =
"L1_TAU8--enabled"
72 for detail
in detailsPerLb[
"Global"]:
73 metadata.append({detail : detailsPerLb[
"Global"][detail]})
74 metadata.append({
"LumiblockDetails" : detailsPerLb[
"PerLb"]})
75 if metadata[1][
'Details']:
76 metadata[1][
'Details'] +=
" "
78 metadata[1][
'Details'] =
""
79 metadata[1][
'Details'] +=
"Monitored time {4}h: {0} - {1} max <mu> {2} deadtime {3}".
format(
80 detailsPerLb[
"Global"][
"DataRangeStart"], detailsPerLb[
"Global"][
"DataRangeEnd"],
81 detailsPerLb[
"Global"][
"GlobalMaxPileup"], detailsPerLb[
"Global"][
"GlobalMeanDeadtime"],
82 detailsPerLb[
"Global"][
"DataRangeDuration"])
84 log.error(
"Reading lumiblock details for TRP failed!")
86 metadata.append({
'Histogram under/overflows' : processingWarnings})
88 metadata.append({
'HLTMenu' : json.loads(
str(metatree.HLTMenu))})
90 with open(
'metadata.json',
'w')
as outMetaFile:
92 metafile[
'text'] =
'metadata'
93 metafile[
'children'] = metadata
94 json.dump(obj=metafile, fp=outMetaFile, indent=2, sort_keys=
True)
98 ''' @brief Create summery of under/overflows based on passed warnings
101 log.debug(
"Received %s warnings", len(warnings))
102 for entry
in warnings:
103 histFullName = entry.split(
" ")[-1]
104 histType = histFullName.split(
"_")[-2] +
"_" + histFullName.split(
"_")[-1]
105 summary = entry.split(
" ")[-1].
split(
"HLT")[0] +
"HLT"
107 if "LumiBlock" in summary:
109 summary = summary.split(
'_', 1)[1]
110 summary = summary.split(
'_', 1)[1]
111 elif "All" in summary:
113 summary = summary.split(
'_', 1)[1]
115 entryName = summary +
"_" + histType
116 if entryName
in histogramStats:
117 histogramStats[entryName] += 1
119 histogramStats[entryName] = 1
121 histogramStatsStr = []
122 for name, value
in histogramStats.items():
123 histogramStatsStr.append(
"{0}: {1} histograms with over/underflows".
format(name, value))
125 return {
"Summary": histogramStatsStr}
129 ''' @brief Filter out the histograms to ignore in underflow check
133 if "FStep" in histogramName
and "Time" in histogramName:
134 log.debug(
"Filter %s underflow will be ignored", histogramName)
141 ''' @brief Retrieve additional run metadata from oks repository
146 oksTag =
"r{0}@{1}".
format(runNumber, partition)
147 log.info(
"Cloning tdaq-09-04-00 Tag " + oksTag)
149 os.system(
"git clone https://gitlab.cern.ch/atlas-tdaq-oks/p1/tdaq-09-04-00.git --branch " + oksTag +
" --single-branch")
153 if partition ==
"TDAQ":
154 partitionRoot = ET.parse(
'tdaq-09-04-00/daq/partitions/TDAQ.data.xml').getroot()
155 hltRoot = ET.parse(
'tdaq-09-04-00/daq/segments/HLT/HLT-TDAQ.data.xml').getroot()
156 elif partition ==
"ATLAS":
157 partitionRoot = ET.parse(
'tdaq-09-04-00/combined/partitions/ATLAS.data.xml').getroot()
158 hltRoot = ET.parse(
'tdaq-09-04-00/daq/segments/HLT/HLT-internal.data.xml').getroot()
159 except FileNotFoundError
as e:
160 log.warning(
"OKS files not available: {0}".
format(e))
168 for computerName
in hostname.split(
","):
170 if rackName
not in racksToComp:
171 racksToComp[rackName] =
list()
172 racksToComp[rackName].
append(computerName)
174 hostname =
",".
join(racksToComp.keys())
176 for rack
in hostname.split(
","):
179 metadataDict = [{
'Hostname' : rack},
180 {
'Forks' : hltRoot.findall(
"./*[@id='{0}']/*[@name='numForks']".
format(hltApplication))[0].
get(
"val")},
181 {
'Threads' : hltRoot.findall(
"./*[@id='{0}']/*[@name='numberOfAthenaMTThreads']".
format(hltApplication))[0].
get(
"val")},
182 {
'Slots' : hltRoot.findall(
"./*[@id='{0}']/*[@name='numberOfEventSlots']".
format(hltApplication))[0].
get(
"val")}]
184 if rack
in racksToComp:
185 metadataDict.append({
'Computers' :
str(racksToComp[rack])})
187 oksMetadata.append(metadataDict)
190 os.system(
"rm -rf tdaq-09-04-00")
195 ''' @brief Find HLT application based on hostname and disabled segments
201 for segment
in hltRoot.findall(
"./*[@class='TemplateSegment']/*[@name='Racks']/*[@id='{0}'].../...".
format(hostname)):
202 segments.append(segment.get(
"id"))
206 for segment
in hltRoot.findall(
"./*[@class='Segment']/*[@name='Segments']/*[@class='TemplateSegment']"):
207 segments.append(segment.get(
"id"))
209 log.debug(
"Found segments {0}".
format(segments))
212 for segment
in partitionRoot.findall(
"./*[@class='Partition']/*[@name='Disabled']/*[@class='TemplateSegment']"):
213 if segment.get(
"id")
in segments:
214 segments.remove(segment.get(
"id"))
216 if len(segments) > 1:
217 log.warning(
"Found more than one enabled segment, will use {0}".
format(segments[0]))
219 return hltRoot.findall(
"./*[@id='{0}']/*[@name='Applications']/*[@class='HLTRCApplication']".
format(segments[0]))[0].
get(
"id")
223 ''' Find rack for computer name '''
226 m = re.search(
r'pc-tdq-tpu-(.*?)\.cern\.ch', computerName)
228 computerNum = m.group(1)
229 rackNumber = computerNum[0:2]
230 return "tpu-rack-{0}".
format(rackNumber)
232 log.warning(
"Cannot retrieve rack number from {0}".
format(computerName))
238 Returns list of config keys read from COOL:
241 SMK - Super Master Key
242 HLTPSK - HLT Prescale keys
243 LVL1PSK - L1 Prescale keys
248 from TrigConfStorage.TriggerCoolUtil
import TriggerCoolUtil
249 dbconn = TriggerCoolUtil.GetConnection(
"CONDBR2")
250 configKeys = TriggerCoolUtil.getHLTConfigKeys(dbconn, [[runNumber, runNumber]])
252 if configKeys
and runNumber
in configKeys.keys():
253 configKeys = configKeys[runNumber]
255 configMetadata.append({
'DB' : configKeys[
'DB']})
256 configMetadata.append({
'Release' : configKeys[
'REL']})
257 configMetadata.append({
'SMK' : configKeys[
'SMK']})
259 configMetadata.append({
'HLTPSK' :
str(TriggerCoolUtil.getHLTPrescaleKeys(dbconn, [[runNumber, runNumber]])[runNumber][
'HLTPSK2'])})
260 configMetadata.append({
'LVL1PSK' :
str(TriggerCoolUtil.getL1ConfigKeys(dbconn, [[runNumber, runNumber]])[runNumber][
'LVL1PSK'])})
263 log.warning(
"Config keys not found in COOL")
265 return configMetadata
271 Returns list of config keys read from AMI Tag:
274 SMK - Super Master Key
275 HLTPSK - HLT Prescale keys
276 LVL1PSK - L1 Prescale keys
283 import pyAMI.atlas.api
as AtlasAPI
284 except ModuleNotFoundError:
285 log.warning(
"Unable to import AMIClient from pyAMI. Maybe you didn't do localSetupPyAMI?")
286 return configMetadata
288 amiclient = pyAMI.client.Client(
'atlas')
291 command = [
'AMIGetAMITagInfo',
'-amiTag="%s"' % amiTag,
'-cached' ]
292 amiTagInfo = amiclient.execute(command, format =
'dict_object').get_rows(
'amiTagInfo')[0]
294 configMetadata.append({
'Release' : amiTagInfo[
'SWReleaseCache']})
295 configMetadata.append({
'SMK' : amiTagInfo[
'DBsmkey']
if "DBsmkey" in amiTagInfo
else None})
296 configMetadata.append({
'DB' : amiTagInfo[
'DBserver']
if "DBserver" in amiTagInfo
else None})
297 configMetadata.append({
'HLTPSK' : amiTagInfo[
'DBhltpskey']
if "DBhltpskey" in amiTagInfo
else None})
298 configMetadata.append({
'LVL1PSK' : amiTagInfo[
'DBl1pskey']
if "DBl1pskey" in amiTagInfo
else None})
300 return configMetadata
303 def readDetailsFromTRP(inputFile, runNumber, maxRanges, itemName="L1_eEM26M--enabled", server="https://atlasop.cern.ch
"):
304 log.info("Reading run details from TRP")
310 for timeRange
in inputFile.GetListOfKeys():
311 rangeObj = timeRange.ReadObj()
312 if not rangeObj.IsA().InheritsFrom(ROOT.TDirectory.Class()):
continue
313 rangeName = rangeObj.GetName()
315 for table
in rangeObj.GetListOfKeys():
316 tableObj = table.ReadObj()
317 if "Global" not in tableObj.GetName():
continue
319 dirKey =
set(key.ReadObj().GetName()
for key
in tableObj.GetListOfKeys()
if key.ReadObj().GetName().startswith(
'LumiBlock'))
320 lumiBlockDict[rangeName] =
sorted(dirKey)
322 if not lumiBlockDict:
323 log.error(
"No lumiblocks were found in the input file")
327 from DQUtils.sugar
import RunLumi
328 from time
import ctime
329 from PyCool
import cool
330 from TrigConfStorage.TriggerCoolUtil
import TriggerCoolUtil
331 dbconn = TriggerCoolUtil.GetConnection(
"CONDBR2")
335 f = dbconn.getFolder(
"/TRIGGER/LUMI/LBLB" )
336 for lbRange
in lumiBlockDict:
337 startLb =
int(
min(lumiBlockDict[lbRange]).
replace(
'LumiBlock_',
''))
338 endLb =
int(
max(lumiBlockDict[lbRange]).
replace(
'LumiBlock_',
''))
339 log.debug(
"For range {0} first lumiblock is {1} and last {2}".
format(lbRange, startLb, endLb))
341 since =
RunLumi(runNumber, startLb)
342 until =
RunLumi(runNumber, endLb)
344 objs = f.browseObjects(since, until, cool.ChannelSelection(0))
346 objCurrRef = objs.currentRef()
347 startTime =
int(objCurrRef.payload()[
"StartTime"]/1000)
349 while objs.goToNext():
350 objCurrRef = objs.currentRef()
352 endTime =
int(objCurrRef.payload()[
"EndTime"]/1000)
354 lbRangeTsDict[lbRange] = {
"start": startTime,
"end" : endTime}
356 log.debug(
"Read start and end of range {0} from COOL: {1} - {2}".
format(lbRange, ctime(startTime/1E6).
replace(
' ',
'_'), ctime(endTime/1E6).
replace(
' ',
'_')))
360 lbRangeDetailsDict = {}
361 physicsDeadtimeGlobal = []
365 pbeast = libpbeastpy.ServerProxy(server)
367 for lbRange
in lbRangeTsDict:
368 lbStart = lbRangeTsDict[lbRange][
"start"]
369 lbEnd = lbRangeTsDict[lbRange][
"end"]
372 physicsDeadtimeTRP = pbeast.get_data(
'ATLAS',
'L1_Rate',
'DT',
'ISS_TRP.' + itemName,
False, lbStart, lbEnd, 0,
True)
374 if len(physicsDeadtimeTRP) == 0:
375 log.error(
"Deadtime not found for item {0} for range {1}".
format(itemName, lbRange))
376 physicsDeadtimeAvg = -1
378 physicsDeadtimeTRP = physicsDeadtimeTRP[0].data[
'ISS_TRP.' + itemName]
379 physicsDeadtimeArray = []
380 for entry
in physicsDeadtimeTRP:
382 if entry.ts < lbStart
or entry.ts > lbEnd:
384 if type(entry.value)
is not float:
387 physicsDeadtimeArray.append(entry.value)
388 physicsDeadtimeGlobal.append(entry.value)
390 physicsDeadtimeAvg =
sum(physicsDeadtimeArray)/len(physicsDeadtimeArray)
if len(physicsDeadtimeArray) > 0
else 1.
393 pileupPbeast = pbeast.get_data(
'OLC',
'OCLumi',
'Mu',
'OLC.OLCApp/ATLAS_PREFERRED_LBAv_PHYS',
False, lbStart, lbEnd)[0].data[
'OLC.OLCApp/ATLAS_PREFERRED_LBAv_PHYS']
395 for entry
in pileupPbeast:
396 if entry.ts < lbStart
or entry.ts > lbEnd:
398 if type(entry.value)
is not float:
401 pileupArr.append(entry.value)
402 pileupGlobal.append(entry.value)
404 pileupAvg =
sum(pileupArr)/len(pileupArr)
if len(pileupArr) > 0
else -1
405 lbRangeDetailsDict[lbRange] = {
"avgPileup" :
round(pileupAvg, 3),
"minPileup" :
round(
min(pileupArr), 3),
406 "maxPileup" :
round(
max(pileupArr), 3),
"deadtime" :
round(physicsDeadtimeAvg, 3)}
408 except ImportError
as e:
409 log.error(
"The pbeast python library was not found! Remember to setup tdaq release!")
412 except RuntimeError
as e:
413 if "Sign in to your account" in str(e):
414 log.error(
"PBeast authentication failed! Remember to export pbeast server sso: export PBEAST_SERVER_SSO_SETUP_TYPE=AutoUpdateKerberos")
415 elif "cannot create CERN SSO cookie" in str(e):
416 log.error(
"PBeast authentication requires the cookies, please setup")
418 log.error(
"Error when reading from Pbeast! ")
422 log.debug(
"The final lumiblock dictionary is {0}".
format(lbRangeDetailsDict))
424 physicsDeadtimeGlobalAvg =
sum(physicsDeadtimeGlobal)/len(physicsDeadtimeGlobal)
if len(physicsDeadtimeGlobal) > 0
else 1.
425 pileupGlobalAvg =
sum(pileupGlobal)/len(pileupGlobal)
if len(pileupGlobal) > 0
else 1.
427 startTs = lbRangeTsDict[
min(lbRangeTsDict.keys())][
"start"]/1E6
428 endTs = lbRangeTsDict[
max(lbRangeTsDict.keys())][
"end"]/1E6
429 monitoredTime = datetime.timedelta(seconds=(
int(endTs - startTs)))
430 additionalDetails = {
431 "DataRangeStart" : ctime(startTs),
432 "DataRangeEnd" : ctime(endTs),
433 "DataRangeDuration" :
"{0}:{1}".
format(
int(monitoredTime.total_seconds()//3600),
int((monitoredTime.total_seconds()%3600)//60)),
434 "GlobalMeanPileup" :
round(pileupGlobalAvg, 3),
435 "GlobalMinPileup" :
round(
min(pileupGlobal), 3),
436 "GlobalMaxPileup" :
round(
max(pileupGlobal), 3),
437 "GlobalMeanDeadtime" :
round(physicsDeadtimeGlobalAvg, 3)
440 return {
"Global" : additionalDetails,
"PerLb" : lbRangeDetailsDict}