28 except ImportError:
pass
29 from collections
import OrderedDict
32 Version =
"%(prog)s 1.5"
33 __doc__ =
"Prints statistics of the module timings based on the information from the Timing service."
39 """Returns sign(x) * sqrt(abs(x))"""
40 if value >= 0.:
return math.sqrt(value)
41 else:
return -math.sqrt(-value)
46 """Statistics collector.
48 This class accumulates statistics on a single variable.
49 A new entry is added by add(), that allowes an optional weight.
50 At any time, the following information about the sample of x is available:
51 - n(): number of additions
52 - weights(): total weight (matches n() unless weights are specified)
53 - sum(): weighted sum of x
54 - min(): minimum value of x seen so far (None if no entries yet)
55 - max(): maximum value of x seen so far (None if no entries yet)
56 - sumsq(): weighted sum of x^2
57 - average(): weighted average of x (0 if no entries yet)
58 - sqaverage(): weighted average of x^2 (0 if no entries yet)
59 - rms(): the Root Mean Square (including weights)
60 - rms2(): the square of the RMS (including weights)
61 - stdev(): standard deviation (0 if less than two events)
62 - stdevp(): an alias for rms()
64 The construction allows to specify bFloat = false, in which case the
65 accumulators are integral types (int) until a real type value or weight is
85 def add(self, value, weight=1):
88 The addition is treated as integer only if both value and weight are
93 self.
e_sum += weight * value
94 self.
e_sumsq += weight * value**2
99 def n(self):
return self.e_n
101 def sum(self):
return self.e_sum
102 def min(self):
return self.e_min
103 def max(self):
return self.e_max
104 def sumsq(self):
return self.e_sumsq
106 if self.
e_w != 0.:
return float(self.
e_sum)/self.
e_w
112 def rms(self):
return signed_sqrt(self.
rms2())
114 if self.
e_n < 2:
return 0.
115 else:
return self.
rms() * math.sqrt(float(self.
e_n)/(self.
e_n-1))
121 """Event identifier: run, subrun and event numbers."""
122 def run(self):
return self[0]
127 return "run %d subRun %d event %d" \
134 """Module instance identifier: module label and instance name."""
143 """A flexible data structure for per-event information.
145 The object is associated to a specific, unique event.
146 It can represent either the execution of the full event, or of a specific
147 module on that event.
148 The object gathers custom data; the standard data members:
149 - time (default: None): seconds elapsed by the event
150 - module (default: not defined): the module identification
151 If time is None, we assume this event was never completed.
152 The presence of a module data member implies that this object descrivbes a
153 module execution rather than the whole event.
157 self.data.setdefault(
'time',
None)
163 try:
return self.
data[attrName]
164 except KeyError:
raise AttributeError(attrName)
168 try:
return self.
data[
'time']
169 except KeyError:
return None
173 try:
return bool(self.module)
174 except AttributeError:
return False
185 if self.
isModule(): s +=
" module " + str(self.module)
188 if self.
time()
is None: s +=
"(n/a)"
189 else: s +=
"%g s" % self.
time()
197 """Collects statistics about execution time.
199 This class collects statistics about execution time of a module or the whole
201 The timing information is added by add() function, with as argument an
202 instance of EntryDataClass.
203 Optionally, the object can keep track of all the entries separately.
204 The order of insertion of the events is also recorded.
205 By default, this does not happen and only statistics are stored.
207 The sample can be forcibly filled with empty entries. The idea is that one
208 event is added to the sample only when the information about its timing is
209 available. If we are tracking the event keys, we can check if we have all
210 the events and, if some event keys are missing, we can add an empty entry for
211 them so that we have the correct number of enrties in the sample.
212 This is achieved by a call to complete().
213 Note that to keep the order of the events the correct one one should check
214 if the previous event is present or not, and complete() with it, before
215 adding a new event. If the completion is performed after the new event is
216 added, the previous event will be added after the new one, when complete()
219 def __init__(self, moduleKey, bTrackEntries = False):
220 """Constructor: specifies the module we collect information about.
222 If the flag bTrackEntries is true, all the added events are stored singly.
226 self.
entries = OrderedDict()
if bTrackEntries
else None
230 """Adds a time to the sample.
232 The argument data is an instance of EntryDataClass, that includes both
233 event identification and timing information.
234 Its time() is used as the value of the statistic; if the entry has no time
235 (None), the event information is considered to be missing.
238 if data.eventKey
in self.
entries:
return False
239 self.
entries[data.eventKey] = data
241 if not data.isMissing(): Stats.add(self, data.time())
246 """Makes sure that an entry for each of the keys in eventKeys is present.
248 For event keys already known, nothing happens. For new event keys, an
249 empty entry is added at the end of the list, with no time information.
250 Note that the events are added at the bottom of the list, in the relative
253 If we are not tracking the events, nothing happens ever.
255 if self.
entries is None:
return 0
256 if (len(self.
entries) > 1): eventKeys = eventKeys[-1:]
258 for eventKey
in eventKeys:
264 """Returns the list of known event keys (if tracking the events)."""
265 return []
if self.
entries is None else self.entries.keys()
269 """Returns a list of the event statistics (if tracking the events)."""
270 return []
if self.
entries is None else self.entries.values()
274 """Returns the number of recorded entries (throws if not tracking)."""
279 """Returns the number of valid entries (events with timing)."""
284 """Returns whethere there are entries without timing information.
286 Note: throws if not tracking events.
292 """Prints the collected information into a list.
294 The list of strings includes a statistics ID (based on the key), an
295 average time, a relative RMS in percent, the total time and the recorded
296 the number of events with timing information and the timing extrema.
298 The format dictionary can contain format directives, for future use (no
299 format directive is currently supported).
301 if isinstance(self.
key, basestring): name = str(self.
key)
302 else: name = str(self.
key)
303 if (self.
n() == 0)
or (self.
sum() == 0.):
304 return [ name,
"n/a" ]
305 RMS = self.
rms()
if (self.
n() != 0)
else 0.
309 "(RMS %4.1f%%)" % (RMS / self.
average() * 100.),
310 "total %g\"" % self.
sum(),
"(%d events:" % self.
n(),
311 "%g" % self.
min(),
"- %g)" % self.
max(),
316 """Prints the collected information into a list.
318 The list of strings includes a statistics ID (based on the key), and
319 a time entry for each of the events stored (with holes for the events
321 The format dictionary can contain format directives; the ones supported
323 - 'max_events' (int): limit the number of events to the first max_events
324 (by default, all the available entries are printed)
325 - 'format' (string, default: '%g'): the C-style formatting string for the
328 if isinstance(self.
key, basestring): name = str(self.
key)
329 else: name = str(self.
key)
332 format_str = format_.get(
'format',
'%g')
333 if not self.
entries:
return [ name, ] + [
"n/a", ] * n
336 for i, entry
in enumerate(self.entries.values()):
338 if entry
is None or entry.isMissing(): output.append(
"n/a")
339 else: output.append(format_str % entry.time())
348 """A class collecting timing information from different modules.
350 This is mostly a dictionary structure, but it is sorted.
351 The supported interface includes access by key (dictionary-like) or by
352 position (list-like).
375 if isinstance(key, int):
return self.moduleList.__getitem__(key)
376 else:
return self.moduleStats.__getitem__(key)
379 if isinstance(key, int):
383 "Trying to overwrite stats of module %s at #%d with module %s"
388 self.moduleList.extend([
None ] * (key - len(self.
moduleList) + 1))
394 index = self.moduleList.index(stats)
397 self.moduleList.append(
None)
411 RuntimeError.__init__(self, msg)
417 """Parses a line to extract module timing information.
419 The line must be known to contain module timing information.
420 The function returns a EntryDataClass including the timing information, or
421 raises a FormatError if the line has no valid format.
425 TimeModule> run: 1 subRun: 0 event: 10 beziertrackercc BezierTrackerModule 0.231838
427 Tokens = line.split()
435 EventKey =
EventKeyClass((int(Tokens[2]), int(Tokens[4]), int(Tokens[6])))
437 time=float(Tokens[9])
440 "TimeModule format not recognized: '%s' (%s)" % (line, str(e)),
441 type=
"Module", event=EventKey, module=ModuleKey
446 if (Tokens[0] !=
'TimeModule>') \
447 or (Tokens[1] !=
'run:') \
448 or (Tokens[3] !=
'subRun:') \
449 or (Tokens[5] !=
'event:') \
450 or (len(Tokens) != 10) \
453 (
"TimeModule format not recognized: '%s'" % line, type=
"Module")
461 """Parses a line to extract event timing information.
463 The line must be known to contain event timing information.
464 The function returns a EntryDataClass including the timing information, or
465 raises a FormatError if the line has no valid format.
469 TimeEvent> run: 1 subRun: 0 event: 10 0.231838
471 Tokens = line.split()
476 EventKey =
EventKeyClass((int(Tokens[2]), int(Tokens[4]), int(Tokens[6])))
477 time = float(Tokens[7])
480 "TimeEvent format not recognized: '%s' (%s)" % (line, str(e)),
481 type=
"Event", event=EventKey
485 if (Tokens[0] !=
'TimeEvent>') \
486 or (Tokens[1] !=
'run:') \
487 or (Tokens[3] !=
'subRun:') \
488 or (Tokens[5] !=
'event:') \
489 or (len(Tokens) != 8) \
491 raise FormatError(
"TimeEvent format not recognized: '%s'" % line,
492 type=
"Event", event=EventKey)
500 """Open a file (possibly a compressed one).
502 Support for modes other than 'r' (read-only) are questionable.
504 if Path.endswith('.bz2'): return bz2.BZ2File(Path, mode)
505 if Path.endswith('.gz'): return gzip.GzipFile(Path, mode)
506 return open(Path, mode)
510 def ParseInputFile(InputFilePath, AllStats, EventStats, options):
511 """Parses a log file.
513 The art log file at InputFilePath
is parsed.
514 The per-module statistics are added to the existing
in AllStats (an instance
515 of JobStatsClass), creating new ones
as needed. Similarly, per-event
516 statistics are added to EventStats (a TimeModuleStatsClass instance).
518 options
class can contain the following members:
519 - Permissive (default: false): do
not bail out when a format error
is found;
520 the entry
is typically skipped. This often happens because the output line
521 of the timing information
is interrupted by some other output.
522 - MaxEvents (default: all events): collect statistics
for at most MaxEvents
523 events (always the first ones)
524 - CheckDuplicates (default: false): enables the single-event tracking, that
525 allows to check
for duplicates
527 It returns the number of errors encountered.
529 def CompleteEvent(CurrentEvent, EventStats, AllStats):
530 """Make sure that CurrentEvent
is known to all stats.
"""
531 EventStats.complete(( CurrentEvent, ))
532 for ModuleStats in AllStats:
533 ModuleStats.complete(EventStats.getEvents())
537 LogFile = OPEN(InputFilePath, 'r')
545 if line == LastLine:
continue
548 if line.startswith(
"TimeModule> "):
552 except FormatError, e:
554 msg =
"Format error on '%s'@%d" % (InputFilePath, iLine + 1)
555 try: msg +=
" (%s)" % str(e.data[
'type'])
556 except KeyError:
pass
557 try: msg +=
", for event " + str(e.data[
'event'])
558 except KeyError:
pass
559 try: msg +=
", module " + str(e.data[
'module'])
560 except KeyError:
pass
561 print >>sys.stderr, msg
562 if not options.Permissive:
raise
567 ModuleStats = AllStats[TimeData.module]
569 ModuleStats = TimeModuleStatsClass \
570 (TimeData.module, bTrackEntries=options.CheckDuplicates)
571 AllStats[TimeData.module] = ModuleStats
574 ModuleStats.add(TimeData)
575 elif line.startswith(
"TimeEvent> "):
578 except FormatError, e:
580 msg =
"Format error on '%s'@%d" % (InputFilePath, iLine + 1)
581 try: msg +=
" (%s)" % str(e.data[
'type'])
582 except KeyError:
pass
583 try: msg +=
", for event " + str(e.data[
'event'])
584 except KeyError:
pass
585 try: msg +=
", module " + str(e.data[
'module'])
586 except KeyError:
pass
587 print >>sys.stderr, msg
588 if not options.Permissive:
raise
592 EventStats.add(TimeData)
593 if (options.MaxEvents >= 0) \
594 and (EventStats.n() >= options.MaxEvents):
595 if CurrentEvent: CompleteEvent(CurrentEvent, EventStats, AllStats)
601 if (CurrentEvent != TimeData.eventKey):
602 if TimeData
and CurrentEvent:
603 CompleteEvent(CurrentEvent, EventStats, AllStats)
604 CurrentEvent = TimeData.eventKey
607 if CurrentEvent: CompleteEvent(CurrentEvent, EventStats, AllStats)
618 """A list with the maximum length of items seen.
620 Facilitates the correct sizing of a table in text mode.
622 When a list of strings is add()ed, for each position in the list the length
623 of the string in that position is compared to the maximum one seen so far in
624 that position, and that maximum value is updated if proper.
634 self.maxlength.extend([
None ] * (iItem + 1 - len(self.
maxlength)))
637 itemlength = len(str(item))
638 if maxlength < itemlength: self.
maxlength[iItem] = itemlength
650 """Returns the string s centered in a width w, padded by f on both sides."""
651 leftFillerWidth = max(0, w - len(s)) / 2
652 return f * leftFillerWidth + s + f * (w - leftFillerWidth)
656 """Returns the string s in a width w, padded by f on the right."""
657 return s + f * max(0, w - len(s))
660 """Returns the string s in a width w, padded by f on the left."""
661 return f * max(0, w - len(s)) + s
664 """Recomputes the spaces between the words in s so that they fill a width w.
666 The original spacing is lost. The string is split in words by str.split().
667 The character f is used to create the filling spaces between the words.
668 Note that the string can result longer than w if the content is too long.
675 spaceSize = max(1., float(f - sum(map(len, tokens))) / (len(tokens) - 1))
680 for token
in tokens[1:]:
681 totalSpace += spaceSize
682 tokenSpace = int(totalSpace - assignedSpace)
683 s += f * tokenSpace + token
684 assignedSpace += tokenSpace
686 assert assignedSpace == w
692 """Formats list of data in a table"""
695 Each format specification applies to one item in each row.
696 If no format specification is supplied for an item, the last used format
697 is applied. By default, that is a plain conversion to string.
709 class CatchAllLines(LineIdentifierClass):
715 TabularAlignmentClass.LineIdentifierClass.__init__(self)
716 if isinstance(lineno, int): self.
lineno = [ lineno ]
717 else: self.
lineno = lineno
722 if lineno < 0: lineno = len(rawdata) + lineno
723 return iLine == lineno
728 for lineno
in self.
lineno:
729 if self.
matchLine(lineno, iLine, rawdata): success += 1.
730 if success == 0:
return None
740 if spec
is None: SpecData[
'format'] = str
741 elif isinstance(spec, basestring): SpecData[
'format'] = spec
742 elif isinstance(spec, dict):
744 SpecData.setdefault(
'format', str)
756 raise RuntimeError(
"Format specification %r (#%d) not supported."
759 self.
formats[rowSelector] = formats
765 def AddData(self, data): self.tabledata.extend(data)
766 def AddRow(self, *row_data): self.tabledata.append(row_data)
773 for lineMatcher, format_
in self.formats.items():
774 match_success = lineMatcher(iLine, self.
tabledata)
775 if match_success <= success:
continue
777 success = match_success
792 RowFormats = AllFormats[iRow]
795 for iItem, itemdata
in enumerate(rowdata):
797 Spec = RowFormats[iItem]
799 except IndexError: Spec = LastSpec
801 Formatter = Spec[
'format']
802 if isinstance(Formatter, basestring):
803 ItemContent = Formatter % itemdata
804 elif callable(Formatter):
805 ItemContent = Formatter(itemdata)
807 raise RuntimeError(
"Formatter %r (#%d) not supported."
808 % (Formatter, iItem))
810 LineContent.append(ItemContent)
812 ItemLengths.add(LineContent)
813 TableContent.append(LineContent)
817 for iRow, rowdata
in enumerate(TableContent):
818 RowFormats = AllFormats[iRow]
819 Spec = AllFormats[iRow]
822 Spec = RowFormats[iItem]
824 except IndexError: Spec = LastSpec
826 fieldWidth = ItemLengths[iItem]
827 alignment = Spec.get(
'align',
'left')
828 if alignment ==
'right':
830 elif alignment ==
'justified':
832 elif alignment ==
'center':
836 if Spec.get(
'truncate',
True): alignedItem = alignedItem[:fieldWidth]
838 rowdata[iItem] = alignedItem
845 return [ separator.join(RowContent)
for RowContent
in self.
FormatTable() ]
847 def Print(self, stream = sys.stdout):
856 if __name__ ==
"__main__":
862 Parser = argparse.ArgumentParser(description=__doc__)
863 Parser.set_defaults(PresentMode=
"ModTable")
866 Parser.add_argument(
"LogFiles", metavar=
"LogFile", nargs=
"+",
867 help=
"log file to be parsed")
870 Parser.add_argument(
"--eventtable", dest=
"PresentMode", action=
"store_const",
871 const=
"EventTable", help=
"do not group the pages by node")
872 Parser.add_argument(
"--allowduplicates",
'-D', dest=
"CheckDuplicates",
873 action=
"store_false", help=
"do not check for duplicate entries")
874 Parser.add_argument(
"--maxevents", dest=
"MaxEvents", type=int, default=-1,
875 help=
"limit the number of parsed events to this (negative: no limit)")
876 Parser.add_argument(
"--permissive", dest=
"Permissive", action=
"store_true",
877 help=
"treats input errors as non-fatal [%(default)s]")
878 Parser.add_argument(
'--version', action=
'version', version=Version)
880 options = Parser.parse_args()
882 if options.PresentMode
in (
'EventTable', ):
883 options.CheckDuplicates =
True
892 EventStats = TimeModuleStatsClass \
893 (
"=== events ===", bTrackEntries=options.CheckDuplicates)
899 if options.MaxEvents == 0:
raise NoMoreInput
900 for LogFilePath
in options.LogFiles:
901 nErrors +=
ParseInputFile(LogFilePath, AllStats, EventStats, options)
903 except NoMoreInput:
pass
906 if nErrors > 0:
print >>sys.stderr
911 if (AllStats.MaxEvents() == 0)
and (EventStats.nEntries() == 0):
912 print "No time statistics found."
919 if options.PresentMode ==
"ModTable":
921 OutputTable.AddData([ stats.FormatStatsAsList()
for stats
in AllStats ])
923 OutputTable.AddRow(*EventStats.FormatStatsAsList())
924 elif options.PresentMode ==
"EventTable":
926 OutputTable.SetRowFormats \
927 (OutputTable.LineNo(0), [
None, {
'align':
'center' }])
929 OutputTable.AddRow(
"Module", *range(AllStats.MaxEvents()))
931 OutputTable.AddData([ stats.FormatTimesAsList()
for stats
in AllStats ])
933 OutputTable.AddRow(*EventStats.FormatTimesAsList())
935 raise RuntimeError(
"Presentation mode %r not known" % options.PresentMode)
943 print >>sys.stderr,
"%d errors were found in the input files." % nErrors
auto enumerate(Iterables &&...iterables)
Range-for loop helper tracking the number of iteration.
S join(S const &sep, Coll const &s)
Returns a concatenation of strings in s separated by sep.