ATLAS Offline Software
Toggle main menu visibility
Loading...
Searching...
No Matches
Trigger
TrigValidation
TrigValTools
bin
chainDump.py
Go to the documentation of this file.
1
#!/usr/bin/env python
2
#
3
# Copyright (C) 2002-2025 CERN for the benefit of the ATLAS collaboration
4
#
5
6
'''Script to dump trigger counts to a text file'''
7
8
import
sys
9
import
argparse
10
import
logging
11
import
json
12
import
yaml
13
import
ROOT
14
from
collections
import
defaultdict
15
16
total_events_key =
'TotalEventsProcessed'
17
column_width = 10
# width of the count columns for print out
18
name_width = 50
# width of the item name column for print out
19
20
# Store defaultdict as dict in yaml files
21
from
yaml.representer
import
Representer
22
yaml.add_representer(defaultdict, Representer.represent_dict)
23
24
def
get_parser
():
25
parser = argparse.ArgumentParser(usage=
'%(prog)s [options]'
,
26
description=__doc__)
27
parser.add_argument(
'-f'
,
'--inputFile'
,
28
metavar=
'PATH'
,
29
default=
'expert-monitoring.root'
,
30
help=
'Name of input root file'
)
31
parser.add_argument(
'-r'
,
'--referenceFile'
,
32
metavar=
'PATH'
,
33
help=
'Name of reference root file'
)
34
parser.add_argument(
'-v'
,
'--verbose'
,
35
action=
'store_true'
,
36
help=
'Increase output verbosity'
)
37
parser.add_argument(
'-p'
,
'--printOnly'
,
38
action=
'store_true'
,
39
default=
False
,
40
help=
'Print counts instead of saving to file'
)
41
parser.add_argument(
'-d'
,
'--diffOnly'
,
42
action=
'store_true'
,
43
default=
False
,
44
help=
'Only store out of tolerance results (does not change JSON)'
)
45
parser.add_argument(
'--json'
,
46
metavar=
'PATH'
,
47
nargs=
'?'
,
48
const=
'chainDump.json'
,
49
help=
'Save outputs also to a json file with the given name or %(const)s if no name is given'
)
50
parser.add_argument(
'--yaml'
,
51
metavar=
'PATH'
,
52
nargs=
'?'
,
53
const=
'chainDump.yml'
,
54
help=
'Produce a small yaml file including condensed counts information for test file only '
55
'(no ref) with the given name or %(const)s if no name is given'
)
56
parser.add_argument(
'--yamlL1'
,
57
action=
'store_true'
,
58
help=
'Include the L1 count information to the yaml file'
)
59
parser.add_argument(
'--fracTolerance'
,
60
metavar=
'FRAC'
,
61
type=float,
62
default=0.001,
63
help=
'Tolerance as a fraction, default = %(default)s. '
64
'Flagged diffs must exceed all tolerances'
)
65
parser.add_argument(
'--intTolerance'
,
66
metavar=
'NUM'
,
67
type=int,
68
default=2,
69
help=
'Tolerance as a number of counts, default = %(default)s. '
70
'Flagged diffs must exceed all tolerances'
)
71
parser.add_argument(
'--countHists'
,
72
metavar=
'HISTS'
,
73
nargs=
'+'
,
74
default=[
75
'HLTFramework/TrigSignatureMoni/SignatureAcceptance'
,
76
'HLTFramework/TrigSignatureMoni/../TrigSignatureMoni/SignatureAcceptance'
,
77
'HLTFramework/../HLTFramework/TrigSignatureMoni/SignatureAcceptance'
,
78
'TrigSteer_HLT/ChainAcceptance'
,
79
'TrigSteer_HLT/NumberOfActiveTEs'
,
80
'HLTFramework/TrigSignatureMoni/DecisionCount'
,
81
'CTPSimulation/L1ItemsAV'
,
82
'L1/CTPSimulation/output/tavById'
],
83
help=
'Histograms to use for counts dump. All existing '
84
'histograms from the list are used, default = %(default)s'
)
85
parser.add_argument(
'--totalHists'
,
86
metavar=
'HISTS'
,
87
nargs=
'+'
,
88
default=[
89
'TrigSteer_HLT/NInitialRoIsPerEvent'
,
90
'HLTFramework/HLTSeeding/RoIs_eEM/count'
],
91
help=
'Histograms to use for total events. First existing '
92
'histogram from the list is used, default = %(default)s'
)
93
parser.add_argument(
'--histDict'
,
94
metavar=
'DICT'
,
95
nargs=
'+'
,
96
default=[
97
'HLTFramework/TrigSignatureMoni/SignatureAcceptance:HLTChain'
,
98
'HLTFramework/TrigSignatureMoni/../TrigSignatureMoni/SignatureAcceptance:HLTExpress'
,
99
'HLTFramework/../HLTFramework/TrigSignatureMoni/SignatureAcceptance:HLTStep'
,
100
'TrigSteer_HLT/ChainAcceptance:HLTChain'
,
101
'TrigSteer_HLT/NumberOfActiveTEs:HLTTE'
,
102
'HLTFramework/TrigSignatureMoni/DecisionCount:HLTDecision'
,
103
'CTPSimulation/L1ItemsAV:L1AV'
,
104
'L1/CTPSimulation/output/tavById:L1AV'
],
105
help=
'Dictionary defining names of output text files for each '
106
'histogram, default = %(default)s'
)
107
parser.add_argument(
'--printHeader'
,
108
action=
'store_true'
,
109
default=
False
,
110
help=
'Add title of columns to the output txt (just for readability)'
)
111
return
parser
112
113
114
def
open_root_file
(file_path):
115
f = ROOT.TFile(file_path)
116
if
f.IsOpen()
and
not
f.IsZombie():
117
return
f
118
else
:
119
return
None
120
121
122
def
load_histograms
(root_file, hist_paths):
123
hist_dict = {}
124
for
hist_path
in
hist_paths:
125
h = root_file.Get(hist_path)
126
if
not
isinstance(h, ROOT.TH1):
127
logging.debug(
'Cannot open histogram %s, skipping'
, hist_path)
128
continue
129
logging.debug(
'Loaded histogram %s'
, hist_path)
130
hist_dict[hist_path] = h
131
return
hist_dict
132
133
134
def
get_counts
(hist, rowLabel='Output'):
135
'''
136
Extract {xlabel, value} dictionary from a histogram. Values are stored as integers.
137
If histogram is 2D, the y-bin labelled rowLabel is used to extract the value.
138
'''
139
140
nbinsx = hist.GetNbinsX()
141
nbinsy = hist.GetNbinsY()
142
outputRow =
None
# Default to last row if 'Output' not found
143
for
bin
in
range(1, nbinsy):
144
if
hist.GetYaxis().GetBinLabel(bin) == rowLabel:
145
outputRow = bin
146
break
147
148
counts = {}
149
for
b
in
range(1, nbinsx+1):
150
label = hist.GetXaxis().GetBinLabel(b)
151
if
not
label:
152
logging.debug(
'Bin %d in histogram %s has no label, skipping'
, b, hist.GetName())
153
continue
154
155
value = hist.GetBinContent(b)
if
hist.GetDimension() == 1
else
hist.GetBinContent(b, outputRow
or
nbinsy)
156
counts[label] = int(value)
157
158
return
counts
159
160
def
get_2D_counts
(hist):
161
'''
162
Extract {xlabel_ylabel, value} dictionary from a histogram. Values are stored as integers.
163
'''
164
nbinsx = hist.GetNbinsX()
165
nbinsy = hist.GetNbinsY()
166
counts = {}
167
for
x
in
range(1, nbinsx+1):
168
label = hist.GetXaxis().GetBinLabel(x)
169
if
not
label:
170
logging.debug(
'Bin %d in histogram %s has no label, skipping'
, x, hist.GetName())
171
continue
172
173
for
y
in
range(3, nbinsy):
174
rowName = hist.GetYaxis().GetBinLabel(y)
175
# Get only steps and skip the base rows
176
if
rowName
in
[
'Input'
,
'AfterPS'
,
'Output'
,
'Express'
]:
177
continue
178
name = label +
'_'
+ rowName
179
name = name.replace(
' '
,
''
)
180
value = hist.GetBinContent(x, y)
181
counts[name] = int(value)
182
183
return
counts
184
185
def
make_counts_json_dict
(in_counts, ref_counts):
186
counts = {}
187
all_keys =
set
(in_counts)
188
all_keys.update(ref_counts)
189
for
k
in
sorted(all_keys):
190
v = in_counts[k]
if
k
in
in_counts
else
'n/a'
191
ref_v = ref_counts[k]
if
k
in
ref_counts
else
'n/a'
192
counts[k] = {
193
'count'
: v,
194
'ref_count'
: ref_v,
195
'ref_diff'
:
'n/a'
# Filled in compare_ref
196
}
197
return
counts
198
199
200
def
parse_name_dict
(name_dict_as_list):
201
name_dict = {}
202
for
kv
in
name_dict_as_list:
203
kv_split = kv.split(
':'
)
204
if
len(kv_split) < 2:
205
continue
206
name_dict[kv_split[0]] = kv_split[1]
207
return
name_dict
208
209
210
def
get_text_name
(hist_name, name_dict):
211
if
hist_name
in
name_dict:
212
return
name_dict[hist_name]
213
else
:
214
return
hist_name.replace(
'/'
,
'_'
)
215
216
217
def
count_diff
(count_in, count_ref, total_in, total_ref, thr_frac, thr_num):
218
# normalise input counts to total events in reference
219
count_in_norm = (count_in / float(total_in)) * total_ref
220
frac = count_in_norm / float(count_ref)
if
count_ref != 0
else
None
221
222
num_diff = abs(count_in_norm - count_ref) > thr_num
223
frac_diff = abs(frac - 1.0) > thr_frac
if
frac
else
True
224
225
return
num_diff
and
frac_diff
226
227
228
def
compare_ref
(json_dict, thr_frac, thr_num):
229
results = []
230
in_total = json_dict[total_events_key][
'count'
]
231
ref_total = json_dict[total_events_key][
'ref_count'
]
232
for
text_name
in
sorted(json_dict):
233
if
text_name == total_events_key:
234
continue
235
diff_val = []
# different counts in input and reference
236
missing_ref = []
# input count exists but reference is n/a
237
missing_val = []
# input count is n/a but reference exists
238
for
item_name, item_counts
in
json_dict[text_name][
'counts'
].items():
239
v = item_counts[
'count'
]
240
ref_v = item_counts[
'ref_count'
]
241
if
v ==
'n/a'
:
242
missing_val.append([item_name, v, ref_v])
243
item_counts[
'ref_diff'
] =
True
244
elif
ref_v ==
'n/a'
:
245
missing_ref.append([item_name, v, ref_v])
246
item_counts[
'ref_diff'
] =
True
247
elif
count_diff
(v, ref_v, in_total, ref_total, thr_frac, thr_num):
248
diff_val.append([item_name, v, ref_v])
249
item_counts[
'ref_diff'
] =
True
250
else
:
251
item_counts[
'ref_diff'
] =
False
252
good =
True
253
if
len(diff_val) > 0:
254
good =
False
255
dump =
'\n'
.join(
256
[
' {e[0]:{nw}s} {e[1]:>{w}d} {e[2]:>{w}d}'
.format(
257
e=element, nw=name_width, w=column_width)
for
element
in
diff_val])
258
logging.info(
'%s has %d item(s) out of tolerance:\n%s'
,
259
text_name, len(diff_val), dump)
260
if
(len(missing_ref)) > 0:
261
good =
False
262
dump =
'\n'
.join([
' {e[0]:s}'
.format(e=element)
for
element
in
missing_ref])
263
logging.info(
'%s has %d item(s) missing in the reference:\n%s'
,
264
text_name, len(missing_ref), dump)
265
if
(len(missing_val)) > 0:
266
good =
False
267
dump =
'\n'
.join([
' {e[0]:s}'
.format(e=element)
for
element
in
missing_val])
268
logging.info(
'%s has %d item(s) missing with respect to the reference:\n%s'
,
269
text_name, len(missing_val), dump)
270
if
good:
271
logging.info(
'%s is matching the reference'
, text_name)
272
results.append(0)
273
else
:
274
results.append(1)
275
return
max
(results)
276
277
278
def
print_counts
(json_dict):
279
for
text_name
in
json_dict:
280
if
text_name == total_events_key:
281
logging.info(
'%s: %d'
, text_name, json_dict[text_name][
'count'
])
282
continue
283
hist_name = json_dict[text_name][
'hist_name'
]
284
counts = json_dict[text_name][
'counts'
]
285
no_ref =
True
286
for
item_counts
in
counts.values():
287
if
item_counts[
'ref_count'
] !=
'n/a'
:
288
no_ref =
False
289
break
290
dump_lines = []
291
for
item_name, item_counts
in
counts.items():
292
v = item_counts[
'count'
]
293
line =
' {name:{nw}s} {val:>{w}s}'
.format(name=item_name, val=str(v), nw=name_width, w=column_width)
294
if
not
no_ref:
295
ref_v = item_counts[
'ref_count'
]
296
diff = item_counts[
'ref_diff'
]
297
line +=
' {val:>{w}s}'
.format(val=str(ref_v), w=column_width)
298
if
diff:
299
line +=
' <<<<<<<<<<'
300
dump_lines.append(line)
301
logging.info(
'Writing %s counts from histogram %s:\n%s'
, text_name, hist_name,
'\n'
.join(dump_lines))
302
303
304
def
format_txt_count
(count):
305
if
type
(count)
is
int:
306
return
'{val:>{w}d}'
.format(val=count, w=column_width)
307
elif
type
(count)
is
not
str:
308
logging.error(
'Unexpected count type %s'
,
type
(count))
309
count =
'ERROR'
310
if
count ==
'n/a'
:
311
count =
'-'
312
return
'{val:>{w}s}'
.format(val=count, w=column_width)
313
314
315
def
write_txt_output
(json_dict, diff_only=False, printHeader=False):
316
for
text_name
in
sorted(json_dict):
317
if
text_name == total_events_key:
318
logging.info(
'Writing total event count to file %s.txt'
, text_name)
319
with
open(
'{:s}.txt'
.format(text_name),
'w'
)
as
outfile:
320
outfile.write(
'{:d}\n'
.format(json_dict[text_name][
'count'
]))
321
continue
322
hist_name = json_dict[text_name][
'hist_name'
]
323
logging.info(
'Writing counts from histogram %s to file %s.txt'
, hist_name, text_name)
324
counts = json_dict[text_name][
'counts'
]
325
no_ref =
True
326
for
item_counts
in
counts.values():
327
if
item_counts[
'ref_count'
] !=
'n/a'
:
328
no_ref =
False
329
break
330
with
open(
'{:s}.txt'
.format(text_name),
'w'
)
as
outfile:
331
if
printHeader:
332
line =
'{name:{nw}s}'
.format(name=
'chain'
, nw=name_width)
333
if
not
no_ref:
334
line +=
'{name:{cw}s}'
.format(name=
'test'
, cw=column_width) +
'ref \n'
335
else
:
336
line +=
'test \n'
337
outfile.write(line)
338
for
item_name, item_counts
in
counts.items():
339
v = item_counts[
'count'
]
340
line =
'{name:{nw}s} '
.format(name=item_name, nw=name_width) +
format_txt_count
(v)
341
if
not
no_ref:
342
ref_v = item_counts[
'ref_count'
]
343
diff = item_counts[
'ref_diff'
]
344
line +=
' '
+
format_txt_count
(ref_v)
345
if
diff:
346
line +=
' <<<<<<<<<<'
347
elif
diff_only:
348
line =
None
349
if
line:
350
outfile.write(line+
'\n'
)
351
352
353
def
make_light_dict
(full_dict, includeL1Counts):
354
# 3 nested dictionaries of int
355
light_dict = defaultdict(
lambda
: defaultdict(
lambda
: defaultdict(int)))
356
357
def
extract_steps(in_name, out_name):
358
for
name,c
in
full_dict[in_name][
'counts'
].items():
359
if
c[
'count'
]==0:
360
continue
361
chain_name, chain_step = name.split(
'_Step'
)
362
light_dict[chain_name][out_name][int(chain_step)] = c[
'count'
]
363
364
# Change step dictionary to consecutive list of steps
365
for
chain_name
in
light_dict:
366
steps = light_dict[chain_name][out_name]
367
light_dict[chain_name][out_name] = {i:steps[k]
for
i,k
in
enumerate(sorted(steps))}
368
369
extract_steps(
'HLTStep'
,
'stepCounts'
)
370
extract_steps(
'HLTDecision'
,
'stepFeatures'
)
371
372
# Add total chain count and skip total / groups / streams
373
for
chain_name,c
in
full_dict[
'HLTChain'
][
'counts'
].items():
374
light_dict[chain_name][
'eventCount'
] = c[
'count'
]
375
376
if
any(chain_name.startswith(s)
for
s
in
[
'All'
,
'grp_'
,
'str_'
]):
377
del light_dict[chain_name]
378
379
if
includeL1Counts
and
'L1AV'
in
full_dict:
380
light_dict.update(
381
{name:{
"eventCount"
: counts[
"count"
]}
for
name,counts
in
full_dict[
"L1AV"
][
"counts"
].items()}
382
)
383
384
return
light_dict
385
386
387
def
main
():
388
args =
get_parser
().parse_args()
389
logging.basicConfig(stream=sys.stdout,
390
format=
'%(levelname)-8s %(message)s'
,
391
level=logging.DEBUG
if
args.verbose
else
logging.INFO)
392
393
name_dict =
parse_name_dict
(args.histDict)
394
395
398
399
in_file =
open_root_file
(args.inputFile)
400
if
not
in_file:
401
logging.error(
'Failed to open input file %s'
, args.inputFile)
402
return
1
403
logging.debug(
'Opened input file %s'
, args.inputFile)
404
405
if
args.referenceFile:
406
ref_file =
open_root_file
(args.referenceFile)
407
if
not
ref_file:
408
logging.error(
'Failed to open input file %s'
, args.referenceFile)
409
return
1
410
logging.debug(
'Opened input file %s'
, args.referenceFile)
411
412
415
416
in_hists =
load_histograms
(in_file, args.countHists)
417
if
len(in_hists) == 0:
418
logging.error(
'No count histograms could be loaded.'
)
419
return
1
420
logging.info(
'Loaded count histograms: %s'
, sorted(in_hists))
421
422
in_total_hists =
load_histograms
(in_file, args.totalHists)
423
if
len(in_total_hists) == 0:
424
logging.error(
'No total-events histogram could be loaded'
)
425
return
1
426
items = list(in_total_hists.items())
427
in_total = items[0][1].
GetEntries
()
428
logging.info(
'Loaded total-events histogram %s, number of events: %d'
,
429
items[0][0], in_total)
430
431
ref_hists =
None
432
ref_total_hists =
None
433
ref_total =
None
434
if
args.referenceFile:
435
ref_hists =
load_histograms
(ref_file, args.countHists)
436
logging.info(
'Loaded reference count histograms: %s'
, sorted(ref_hists))
437
missing_refs = [k
for
k
in
in_hists
if
k
not
in
ref_hists]
438
if
len(missing_refs) > 0:
439
logging.error(
'Count histogram(s) %s missing in the reference'
, missing_refs)
440
return
1
441
ref_total_hists =
load_histograms
(ref_file, args.totalHists)
442
if
len(ref_total_hists) == 0:
443
logging.error(
'No total-events reference histogram could be loaded'
)
444
return
1
445
ref_total = list(ref_total_hists.values())[0].
GetEntries
()
446
logging.info(
'Loaded total-events reference histogram %s, number of events: %d'
,
447
list(ref_total_hists.keys())[0], ref_total)
448
449
452
453
json_dict = {
454
total_events_key: {
455
'hist_name'
: list(in_total_hists.keys())[0],
456
'count'
: int(in_total),
457
'ref_count'
: int(ref_total)
if
ref_total
else
'n/a'
458
}
459
}
460
461
for
hist_name, hist
in
in_hists.items():
462
text_name =
get_text_name
(hist_name, name_dict)
463
if
text_name
in
json_dict:
464
logging.error(
465
'Name "%s" assigned to more than one histogram, '
, text_name,
466
'results would be overwritten. Use --countHists and '
,
467
'--histDict options to avoid duplicates. Exiting.'
)
468
469
rowLabel =
'Express'
if
'Express'
in
text_name
else
'Output'
470
counts =
get_2D_counts
(hist)
if
text_name
in
[
'HLTStep'
,
'HLTDecision'
]
else
get_counts
(hist, rowLabel)
471
ref_counts = {}
472
if
ref_hists:
473
ref_hist = ref_hists[hist_name]
474
ref_counts =
get_2D_counts
(ref_hist)
if
text_name
in
[
'HLTStep'
,
'HLTDecision'
]
else
get_counts
(ref_hist, rowLabel)
475
d =
make_counts_json_dict
(counts, ref_counts)
476
477
json_dict[text_name] = {
478
'hist_name'
: hist_name,
479
'counts'
: d
480
}
481
482
485
486
retcode = 0
487
if
args.referenceFile:
488
logging.info(
'Comparing counts to reference'
)
489
retcode =
compare_ref
(json_dict, args.fracTolerance, args.intTolerance)
490
491
if
args.printOnly
and
not
args.diffOnly:
492
logging.info(
'Printing counts instead of dumping to files because of --printOnly option'
)
493
print_counts
(json_dict)
494
495
if
not
args.printOnly:
496
write_txt_output
(json_dict, args.diffOnly, args.printHeader)
497
498
if
args.json:
499
logging.info(
'Writing results to %s'
, args.json)
500
with
open(args.json,
'w'
)
as
outfile:
501
json.dump(json_dict, outfile, sort_keys=
True
)
502
503
if
args.yaml:
504
logging.info(
'Writing results extract to %s'
, args.yaml)
505
light_dict =
make_light_dict
(json_dict, includeL1Counts = args.yamlL1)
506
with
open(args.yaml,
'w'
)
as
outfile:
507
yaml.dump(light_dict, outfile, sort_keys=
True
)
508
509
return
retcode
510
511
512
if
'__main__'
in
__name__:
513
sys.exit(
main
())
GetEntries
TGraphErrors * GetEntries(TH2F *histo)
Definition
TRTCalib_makeplots.cxx:4025
max
#define max(a, b)
Definition
cfImp.cxx:41
set
STL class.
chainDump.get_counts
get_counts(hist, rowLabel='Output')
Definition
chainDump.py:134
chainDump.main
main()
Definition
chainDump.py:387
chainDump.make_light_dict
make_light_dict(full_dict, includeL1Counts)
Definition
chainDump.py:353
chainDump.print_counts
print_counts(json_dict)
Definition
chainDump.py:278
chainDump.get_2D_counts
get_2D_counts(hist)
Definition
chainDump.py:160
chainDump.load_histograms
load_histograms(root_file, hist_paths)
Definition
chainDump.py:122
chainDump.compare_ref
compare_ref(json_dict, thr_frac, thr_num)
Definition
chainDump.py:228
chainDump.get_text_name
get_text_name(hist_name, name_dict)
Definition
chainDump.py:210
chainDump.get_parser
get_parser()
Definition
chainDump.py:24
chainDump.open_root_file
open_root_file(file_path)
Definition
chainDump.py:114
chainDump.format_txt_count
format_txt_count(count)
Definition
chainDump.py:304
chainDump.count_diff
count_diff(count_in, count_ref, total_in, total_ref, thr_frac, thr_num)
Definition
chainDump.py:217
chainDump.parse_name_dict
parse_name_dict(name_dict_as_list)
Definition
chainDump.py:200
chainDump.write_txt_output
write_txt_output(json_dict, diff_only=False, printHeader=False)
Definition
chainDump.py:315
chainDump.make_counts_json_dict
make_counts_json_dict(in_counts, ref_counts)
Definition
chainDump.py:185
type
Generated on
for ATLAS Offline Software by
1.17.0