ATLAS Offline Software
Loading...
Searching...
No Matches
trfValidateRootFile.py
Go to the documentation of this file.
1#!/usr/bin/env python
2
3# Copyright (C) 2002-2026 CERN for the benefit of the ATLAS collaboration
4
5
9
10
11import sys, os
12import logging
13
14from PyUtils import RootUtils
15ROOT = RootUtils.import_root()
16from ROOT import TFile, TTree, TDirectory, TStopwatch
17from ROOT import RNTupleReader
18from PyUtils.PoolFile import isRNTuple
19
20msg = logging.getLogger(__name__)
21
22def checkBranch(branch):
23
24 msg.debug('Checking branch %s ...', branch.GetName())
25
26 nBaskets=branch.GetWriteBasket()
27
28 msg.debug('Checking %s baskets ...', nBaskets)
29
30 for iBasket in range(nBaskets):
31 basket=branch.GetBasket(iBasket)
32 if not basket:
33 msg.warning('Basket %s of branch %s is corrupted.', iBasket, branch.GetName() )
34 return 1
35
36 listOfSubBranches=branch.GetListOfBranches()
37 msg.debug('Checking %s subbranches ...', listOfSubBranches.GetEntries())
38 for subBranch in listOfSubBranches:
39 if checkBranch(subBranch)==1:
40 return 1
41
42 msg.debug('Branch %s looks ok.', branch.GetName())
43 return 0
44
45
47
48 listOfBranches=tree.GetListOfBranches()
49
50 msg.debug('Checking %s branches ...', listOfBranches.GetEntries())
51
52 for branch in listOfBranches:
53 if checkBranch(branch)==1:
54 msg.warning('Tree %s is corrupted (branch %s ).', tree.GetName(), branch.GetName())
55 return 1
56
57 return 0
58
59
60def checkTreeEventWise(tree, printInterval = 150000):
61
62 nEntries=tree.GetEntries()
63
64 msg.debug('Checking %s entries ...', nEntries)
65
66 for i in range(nEntries):
67 if tree.GetEntry(i)<0:
68 msg.warning('Event %s of tree %s is corrupted.', i, tree.GetName())
69 return 1
70
71 # Show a sign of life for long validation jobs: ATLASJT-433
72 if (i%printInterval)==0 and i>0:
73 msg.info('Validated %s events so far ...', i)
74
75 return 0
76
77def checkNTupleEventWise(ntuple, printInterval = 150000):
78
79 try:
80 reader=RNTupleReader.Open(ntuple)
81 except Exception as err:
82 msg.warning('Could not open ntuple %s: %s', ntuple, err)
83 return 1
84
85 nEntries=reader.GetNEntries()
86
87 msg.debug('Checking %s entries ...', nEntries)
88
89 entry = reader.CreateEntry()
90 for i in range(nEntries):
91 try:
92 reader.LoadEntry(i, entry)
93 except Exception as err:
94 msg.warning('Event %s of ntuple %s is corrupted: %s', i, reader.GetDescriptor().GetName(), err)
95 return 1
96
97 # Show a sign of life for long validation jobs: ATLASJT-433
98 if (i%printInterval)==0 and i>0:
99 msg.info('Validated %s events so far ...', i)
100
101 return 0
102
104 """For each cluster, bulk read each top level field.
105 """
106 from ROOT import RException
107
108 try:
109 reader=RNTupleReader.Open(ntuple)
110 except Exception as err:
111 msg.warning('Could not open ntuple %r: %r', ntuple, err)
112 return 1
113
114 try:
115 descriptor = reader.GetDescriptor()
116 msg.debug(f"ntupleName={descriptor.GetName()}")
117
118 model = reader.GetModel()
119 fieldZero = model.GetConstFieldZero()
120 subFields = fieldZero.GetConstSubfields()
121 msg.debug(f"Top level fields number {subFields.size()}")
122 for clusterDescriptor in descriptor.GetClusterIterable():
123 size = int(clusterDescriptor.GetNEntries())
124 if msg.isEnabledFor(logging.DEBUG):
125 msg.debug(f" cluster #{clusterDescriptor.GetId()}"
126 f" firstEntryIndex={clusterDescriptor.GetFirstEntryIndex()}"
127 f" nEntries={size}")
128 clusterRange = ROOT.RNTupleLocalRange(clusterDescriptor.GetId(), 0, size)
129 for field in subFields:
130 if msg.isEnabledFor(logging.DEBUG):
131 msg.debug(f"fieldName={field.GetFieldName()} typeName={field.GetTypeName()}")
132 bulk = model.CreateBulk(field.GetFieldName())
133 values = bulk.ReadBulk(clusterRange)
134 if msg.isEnabledFor(logging.DEBUG):
135 msg.debug(f" values array at {values}")
136 # Be sure that this gets destroyed before field.
137 del bulk
138
139 except RException as err:
140 from traceback import format_exception
141 msg.error("Exception reading ntuple %r\n%s", ntuple, "".join(format_exception(err)))
142 return 1
143
144 return 0
145
146def checkDirectory(directory, the_type, requireTree, depth):
147
148 from PyUtils import PoolFile
149 nentries = None
150 hasMetadata = False
151
152 msg.debug('Checking directory %s ...', directory.GetName())
153
154 listOfKeys=directory.GetListOfKeys()
155
156 msg.debug('Checking %s keys ... ', listOfKeys.GetEntries())
157
158 for key in listOfKeys:
159
160 msg.debug('Looking at key %s ...', key.GetName())
161 msg.debug('Key is of class %s.', key.GetClassName())
162
163 the_object=directory.Get(key.GetName())
164 if not the_object:
165 msg.warning("Can't get object of key %s.", key.GetName())
166 return 1
167
168 if requireTree and not isinstance(the_object, TTree):
169 msg.warning("Object of key %s is not of class TTree!", key.GetName())
170 return 1
171
172 if isinstance(the_object,TTree):
173
174 msg.debug('Checking tree %s ...', the_object.GetName())
175
176 if depth == 0:
177 if PoolFile.PoolOpts.TTreeNames.EventData == the_object.GetName():
178 nentries = the_object.GetEntries()
179 msg.debug(f' contains {nentries} events')
180 elif PoolFile.PoolOpts.TTreeNames.MetaData == the_object.GetName():
181 hasMetadata = True
182 msg.debug(' contains MetaData')
183
184 if the_type=='event':
185 if checkTreeEventWise(the_object)==1:
186 return 1
187 elif the_type=='basket':
188 if checkTreeBasketWise(the_object)==1:
189 return 1
190
191 msg.debug('Tree %s looks ok.', the_object.GetName())
192
193 if isRNTuple(the_object):
194
195 msg.debug('Checking ntuple of key %s ...', key.GetName())
196
197 try:
198 reader=RNTupleReader.Open(the_object)
199 except Exception as err:
200 msg.warning('Could not open ntuple %s: %s', the_object, err)
201 return 1
202
203 if depth == 0:
204 if PoolFile.PoolOpts.RNTupleNames.EventData == reader.GetDescriptor().GetName():
205 nentries = reader.GetNEntries()
206 msg.debug(f' contains {nentries} events')
207 elif PoolFile.PoolOpts.RNTupleNames.MetaData == reader.GetDescriptor().GetName():
208 hasMetadata = True
209 msg.debug(' contains MetaData')
210
211 if the_type=='event':
212 if checkNTupleEventWise(the_object)==1:
213 return 1
214 elif the_type=='basket':
215 if checkNTupleFieldWise(the_object)==1:
216 return 1
217
218 msg.debug('NTuple of key %s looks ok.', key.GetName())
219
220 if isinstance(the_object, TDirectory):
221 if checkDirectory(the_object, the_type, requireTree, depth + 1)==1:
222 return 1
223
224 # Only check if metadata object is available as in standard POOL files
225 if depth == 0 and hasMetadata and checkNEvents(directory.GetName(), nentries)==1:
226 return 1
227 else:
228 msg.debug('Directory %s looks ok.', directory.GetName())
229 return 0
230
231
232def checkFile(fileName, the_type, requireTree):
233
234 msg.info('Checking file %s ...', fileName)
235
236 enabledIMT = False
237 if not ROOT.ROOT.IsImplicitMTEnabled() and os.environ.keys() >= {'TRF_MULTITHREADED_VALIDATION', 'ATHENA_CORE_NUMBER'}:
238 if (nThreads := int(os.environ['ATHENA_CORE_NUMBER'])) >= 0:
239 msg.info(f"Setting the number of implicit ROOT threads to {nThreads}")
240 ROOT.ROOT.EnableImplicitMT(nThreads)
241 enabledIMT = True
242 else:
243 msg.warning(f"Ignored negative ATHENA_CORE_NUMBER ({nThreads})")
244
245 try:
246 file_handle=TFile.Open(fileName)
247 except OSError as err:
248 msg.error('Could not open file %s: %s', fileName, err)
249 return 1
250
251 if not file_handle:
252 msg.warning("Can't access file %s.", fileName)
253 return 1
254
255 if not file_handle.IsOpen():
256 msg.warning("Can't open file %s.", fileName)
257 return 1
258
259 if file_handle.IsZombie():
260 msg.warning("File %s is a zombie.", fileName)
261 file_handle.Close()
262 return 1
263
264 if file_handle.TestBit(TFile.kRecovered):
265 msg.warning("File %s needed to be recovered.", fileName)
266 file_handle.Close()
267 return 1
268
269 if checkDirectory(file_handle, the_type, requireTree, 0)==1:
270 msg.warning("File %s is corrupted.", fileName)
271 file_handle.Close()
272 return 1
273
274 file_handle.Close()
275 msg.info("File %s looks ok.", fileName)
276
277 if enabledIMT:
278 ROOT.ROOT.DisableImplicitMT()
279
280 return 0
281
282
283def checkNEvents(fileName, nEntries):
284 """Check consistency of number of events in file with metadata.
285
286 fileName name of file to check consistency of
287 nEntries number of events in fileName (e.g., obtained by examining event data object)
288 return 0 in case of consistency, 1 otherwise
289 """
290 from PyUtils.MetaReader import read_metadata
291
292 msg.debug('Checking number of events in file %s ...', fileName)
293
294 meta = read_metadata(fileName, mode='lite')[fileName]
295 msg.debug(' according to metadata: {0}'.format(meta["nentries"]))
296 msg.debug(' according to event data: {0}'.format(nEntries))
297 if meta["nentries"] and nEntries and meta["nentries"] != nEntries \
298 or meta["nentries"] and not nEntries \
299 or not meta["nentries"] and nEntries:
300 msg.warning(f' number of events ({nEntries}) inconsistent with metadata ({meta["nentries"]}) in file {fileName!r}.')
301 return 1
302 else:
303 msg.debug(" looks ok.")
304 return 0
305
306def usage():
307 print("Usage: validate filename type requireTree verbosity")
308 print("'type' must be either 'event' or 'basket'")
309 print("'requireTree' must be either 'true' or 'false'")
310 print("'verbosity' must be either 'on' or 'off'")
311
312 return 2
313
314
315def main(argv):
316
317 clock=TStopwatch()
318
319 argc=len(argv)
320
321 if (argc!=5):
322 return usage()
323
324 fileName=argv[1]
325 the_type=argv[2]
326 requireTree=argv[3]
327 verbosity=argv[4]
328
329
330 if the_type!="event" and the_type!="basket":
331 return usage()
332
333 if requireTree=="true":
334 requireTree=True
335 elif requireTree=="false":
336 requireTree=False
337 else:
338 return usage()
339
340 if verbosity=="on":
341 msg.setLevel(logging.DEBUG)
342 elif verbosity=="off":
343 msg.setLevel(logging.INFO)
344 else:
345 return usage()
346
347 rc=checkFile(fileName,the_type, requireTree)
348 msg.debug('Returning %s', rc)
349
350 clock.Stop()
351 clock.Print()
352
353 return rc
354
355
356if __name__ == '__main__':
357
358 ch=logging.StreamHandler(sys.stdout)
359 formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s')
360 ch.setFormatter(formatter)
361 msg.addHandler(ch)
362
363 rc=main(sys.argv)
364 sys.exit(rc)
365
void print(char *figname, TCanvas *c1)
checkNTupleEventWise(ntuple, printInterval=150000)
checkDirectory(directory, the_type, requireTree, depth)
checkTreeEventWise(tree, printInterval=150000)