@@ -294,6 +294,66 @@ def summary(logEntries):
294294 for h , q in quantities :
295295 print_summary (h , q )
296296
297+ ####################
298+ # Read ranges
299+ ####################
300+ def printReadRanges (logEntries ):
301+ for entry in logEntries :
302+ if isinstance (entry , Entries .Read ):
303+ print (f"# id { entry .id } " )
304+ print (f"{ entry .offset } { entry .offset + entry .requested } " )
305+ elif isinstance (entry , Entries .Readv ):
306+ print (f"# id { entry .id } " )
307+ for element in entry .elements :
308+ print (f"{ element .offset } { element .offset + element .requested } " )
309+
310+ ####################
311+ # Map read ranges
312+ ####################
313+ MapChunk = namedtuple ("MapChunk" , ("begin" , "end" , "type" , "content" ))
314+ def searchMapChunk (chunks , offset , size ):
315+ if len (chunks ) == 0 :
316+ return []
317+
318+ offset_end = offset + size
319+ import bisect
320+ i = bisect .bisect_left (chunks , MapChunk (offset , 0 , "" , "" ))
321+ ret = []
322+ if i > 0 and offset < chunks [i - 1 ].end :
323+ ret .append (i - 1 )
324+ while i < len (chunks ) and chunks [i ].begin < offset_end :
325+ ret .append (i )
326+ i += 1
327+
328+ return ret
329+
330+ def printMapReadRanges (logEntries , mapFileName ):
331+ chunks = []
332+ with open (mapFileName ) as f :
333+ import re
334+ # Extract the offset (At:...), size (N=...), type, and content
335+ # (with name and possibly title) of an element in
336+ # TFile::Map("extended") printout
337+ line_re = re .compile ("At:(?P<offset>\d+)\s*N=(?P<size>\d+)\s*(?P<type>\w+).*(?P<content>name:.*$)" )
338+ for line in f :
339+ m = line_re .search (line )
340+ if m :
341+ offset = int (m .group ("offset" ))
342+ chunks .append (MapChunk (offset , offset + int (m .group ("size" )), m .group ("type" ), m .group ("content" )))
343+
344+ for entry in logEntries :
345+ if isinstance (entry , Entries .Read ):
346+ print (f"# id { entry .id } " )
347+ for i in searchMapChunk (chunks , entry .offset , entry .requested ):
348+ ch = chunks [i ]
349+ print (f"{ ch .begin } { ch .end } { ch .type } { ch .content } " )
350+ elif isinstance (entry , Entries .Readv ):
351+ print (f"# id { entry .id } " )
352+ for element in entry .elements :
353+ for i in searchMapChunk (chunks , element .offset , element .requested ):
354+ ch = chunks [i ]
355+ print (f"{ ch .begin } { ch .end } { ch .type } { ch .content } " )
356+
297357####################
298358# Main function
299359####################
@@ -307,6 +367,10 @@ def main(logEntries, args):
307367 if args .readOverlaps :
308368 analyzeReadOverlaps (logEntries , args )
309369 print ()
370+ if args .readRanges :
371+ printReadRanges (logEntries )
372+ if args .mapReadRanges :
373+ printMapReadRanges (logEntries , args .mapReadRanges )
310374 pass
311375
312376####################
@@ -427,6 +491,30 @@ def test_processReadOverlaps(self):
427491 # Value 2 here is debatable
428492 self .assertEqual (result .overlap_count , 2 )
429493
494+ def test_searchMapChunk (self ):
495+ self .assertEqual (searchMapChunk ([], 0 , 10 ), [])
496+
497+ chunks = []
498+ for i in range (0 , 100 ):
499+ chunks .append (MapChunk (i * 100 , i * 100 + 50 , "" , i ))
500+ self .assertEqual (searchMapChunk (chunks , 0 , 10 ), [0 ])
501+ self .assertEqual (searchMapChunk (chunks , 0 , 50 ), [0 ])
502+ self .assertEqual (searchMapChunk (chunks , 0 , 100 ), [0 ])
503+ self .assertEqual (searchMapChunk (chunks , 10 , 50 ), [0 ])
504+ self .assertEqual (searchMapChunk (chunks , 10 , 90 ), [0 ])
505+ self .assertEqual (searchMapChunk (chunks , 9900 , 50 ), [99 ])
506+ self .assertEqual (searchMapChunk (chunks , 9900 , 100 ), [99 ])
507+ self .assertEqual (searchMapChunk (chunks , 9900 , 100 ), [99 ])
508+
509+ self .assertEqual (searchMapChunk (chunks , - 10 , 5 ), [])
510+ self .assertEqual (searchMapChunk (chunks , 50 , 40 ), [])
511+ self .assertEqual (searchMapChunk (chunks , 50 , 50 ), [])
512+ self .assertEqual (searchMapChunk (chunks , 9950 , 10 ), [])
513+
514+ self .assertEqual (searchMapChunk (chunks , 0 , 200 ), [0 , 1 ])
515+ self .assertEqual (searchMapChunk (chunks , 49 , 101 - 49 ), [0 , 1 ])
516+ self .assertEqual (searchMapChunk (chunks , 149 , 301 - 149 ), [1 , 2 , 3 ])
517+
430518def test ():
431519 import sys
432520 unittest .main (argv = sys .argv [:1 ])
@@ -453,12 +541,16 @@ def printHelp():
453541 parser .add_argument ("--summary" , action = "store_true" , help = "Print high-level summary of storage operations" )
454542 parser .add_argument ("--readOrder" , action = "store_true" , help = "Analyze ordering of reads" )
455543 parser .add_argument ("--readOverlaps" , action = "store_true" , help = "Analyze overlaps of reads" )
544+ parser .add_argument ("--readRanges" , action = "store_true" , help = "Print offset ranges of each read element" )
545+ parser .add_argument ("--mapReadRanges" , type = str , default = None , help = "Like --readRanges, but uses the output of TFile::Map() to map the file regions to TFile content. The argument should be a file containing the output of 'edmFileUtil --map'." )
456546 parser .add_argument ("--test" , action = "store_true" , help = "Run internal tests" )
457547
458548 args = parser .parse_args ()
459549 if args .test :
460550 test ()
461551 else :
552+ if args .readRanges and args .mapReadRanges :
553+ parser .error ("Only one of --readRanges and --mapReadRanges can be given" )
462554 if args .filename is None :
463555 parser .error ("filename argument is missing" )
464556 with open (args .filename ) as f :
0 commit comments