From 262fb9256b33ca592655361d4982ad8d49a88ffe Mon Sep 17 00:00:00 2001 From: Skip Montanaro Date: Fri, 21 Apr 2006 02:31:07 +0000 Subject: [PATCH] Allow pstats.Stats creator to specify an alternate to stdout. --- Doc/lib/libprofile.tex | 16 ++--- Lib/pstats.py | 144 ++++++++++++++++++++++------------------- Misc/NEWS | 3 + 3 files changed, 90 insertions(+), 73 deletions(-) diff --git a/Doc/lib/libprofile.tex b/Doc/lib/libprofile.tex index 97c7191dd3f..9ff5ba068b2 100644 --- a/Doc/lib/libprofile.tex +++ b/Doc/lib/libprofile.tex @@ -391,17 +391,17 @@ Analysis of the profiler data is done using this class from the % (This \stmodindex use may be hard to change ;-( ) \stmodindex{pstats} -\begin{classdesc}{Stats}{filename\optional{, \moreargs}} +\begin{classdesc}{Stats}{filename\optional{, \moreargs\optional{, stream=sys.stdout}}} This class constructor creates an instance of a ``statistics object'' from a \var{filename} (or set of filenames). \class{Stats} objects are -manipulated by methods, in order to print useful reports. +manipulated by methods, in order to print useful reports. You may specify +an alternate output stream by giving the keyword argument, \code{stream}. -The file selected by the above constructor must have been created by -the corresponding version of \module{profile} or \module{cProfile}. -To be specific, there is -\emph{no} file compatibility guaranteed with future versions of this -profiler, and there is no compatibility with files produced by other -profilers. +The file selected by the above constructor must have been created by the +corresponding version of \module{profile} or \module{cProfile}. To be +specific, there is \emph{no} file compatibility guaranteed with future +versions of this profiler, and there is no compatibility with files produced +by other profilers. %(such as the old system profiler). If several files are provided, all the statistics for identical diff --git a/Lib/pstats.py b/Lib/pstats.py index 930cc6d17a2..c3a88284ca7 100644 --- a/Lib/pstats.py +++ b/Lib/pstats.py @@ -32,6 +32,7 @@ # CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +import sys import os import time import marshal @@ -58,18 +59,31 @@ class Stats: printed. The sort_stats() method now processes some additional options (i.e., in - addition to the old -1, 0, 1, or 2). It takes an arbitrary number of quoted - strings to select the sort order. For example sort_stats('time', 'name') - sorts on the major key of "internal function time", and on the minor - key of 'the name of the function'. Look at the two tables in sort_stats() - and get_sort_arg_defs(self) for more examples. + addition to the old -1, 0, 1, or 2). It takes an arbitrary number of + quoted strings to select the sort order. For example sort_stats('time', + 'name') sorts on the major key of 'internal function time', and on the + minor key of 'the name of the function'. Look at the two tables in + sort_stats() and get_sort_arg_defs(self) for more examples. - All methods now return "self", so you can string together commands like: + All methods return self, so you can string together commands like: Stats('foo', 'goo').strip_dirs().sort_stats('calls').\ print_stats(5).print_callers(5) """ - def __init__(self, *args): + def __init__(self, *args, **kwds): + # I can't figure out how to explictly specify a stream keyword arg + # with *args: + # def __init__(self, *args, stream=sys.stdout): ... + # so I use **kwds and sqauwk if something unexpected is passed in. + self.stream = sys.stdout + if "stream" in kwds: + self.stream = kwds["stream"] + del kwds["stream"] + if kwds: + keys = kwds.keys() + keys.sort() + extras = ", ".join(["%s=%s" % (k, kwds[k]) for k in keys]) + raise ValueError, "unrecognized keyword args: %s" % extras if not len(args): arg = None else: @@ -96,9 +110,9 @@ class Stats: trouble = 0 finally: if trouble: - print "Invalid timing data", - if self.files: print self.files[-1], - print + print >> self.stream, "Invalid timing data", + if self.files: print >> self.stream, self.files[-1], + print >> self.stream def load_stats(self, arg): if not arg: self.stats = {} @@ -320,7 +334,7 @@ class Stats: if not list: return 0, list - print msg + print >> self.stream, msg if count < len(self.stats): width = 0 for func in list: @@ -330,24 +344,24 @@ class Stats: def print_stats(self, *amount): for filename in self.files: - print filename - if self.files: print + print >> self.stream, filename + if self.files: print >> self.stream indent = ' ' * 8 for func in self.top_level: - print indent, func_get_function_name(func) + print >> self.stream, indent, func_get_function_name(func) - print indent, self.total_calls, "function calls", + print >> self.stream, indent, self.total_calls, "function calls", if self.total_calls != self.prim_calls: - print "(%d primitive calls)" % self.prim_calls, - print "in %.3f CPU seconds" % self.total_tt - print + print >> self.stream, "(%d primitive calls)" % self.prim_calls, + print >> self.stream, "in %.3f CPU seconds" % self.total_tt + print >> self.stream width, list = self.get_print_list(amount) if list: self.print_title() for func in list: self.print_line(func) - print - print + print >> self.stream + print >> self.stream return self def print_callees(self, *amount): @@ -361,8 +375,8 @@ class Stats: self.print_call_line(width, func, self.all_callees[func]) else: self.print_call_line(width, func, {}) - print - print + print >> self.stream + print >> self.stream return self def print_callers(self, *amount): @@ -372,12 +386,12 @@ class Stats: for func in list: cc, nc, tt, ct, callers = self.stats[func] self.print_call_line(width, func, callers, "<-") - print - print + print >> self.stream + print >> self.stream return self def print_call_heading(self, name_size, column_title): - print "Function ".ljust(name_size) + column_title + print >> self.stream, "Function ".ljust(name_size) + column_title # print sub-header only if we have new-style callers subheader = False for cc, nc, tt, ct, callers in self.stats.itervalues(): @@ -386,12 +400,12 @@ class Stats: subheader = isinstance(value, tuple) break if subheader: - print " "*name_size + " ncalls tottime cumtime" + print >> self.stream, " "*name_size + " ncalls tottime cumtime" def print_call_line(self, name_size, source, call_dict, arrow="->"): - print func_std_string(source).ljust(name_size) + arrow, + print >> self.stream, func_std_string(source).ljust(name_size) + arrow, if not call_dict: - print + print >> self.stream return clist = call_dict.keys() clist.sort() @@ -411,30 +425,30 @@ class Stats: else: substats = '%s(%r) %s' % (name, value, f8(self.stats[func][3])) left_width = name_size + 3 - print indent*left_width + substats + print >> self.stream, indent*left_width + substats indent = " " def print_title(self): - print ' ncalls tottime percall cumtime percall', \ - 'filename:lineno(function)' + print >> self.stream, ' ncalls tottime percall cumtime percall', + print >> self.stream, 'filename:lineno(function)' def print_line(self, func): # hack : should print percentages cc, nc, tt, ct, callers = self.stats[func] c = str(nc) if nc != cc: c = c + '/' + str(cc) - print c.rjust(9), - print f8(tt), + print >> self.stream, c.rjust(9), + print >> self.stream, f8(tt), if nc == 0: - print ' '*8, + print >> self.stream, ' '*8, else: - print f8(tt/nc), - print f8(ct), + print >> self.stream, f8(tt/nc), + print >> self.stream, f8(ct), if cc == 0: - print ' '*8, + print >> self.stream, ' '*8, else: - print f8(ct/cc), - print func_std_string(func) + print >> self.stream, f8(ct/cc), + print >> self.stream, func_std_string(func) class TupleComp: """This class provides a generic function for comparing any two tuples. @@ -549,7 +563,7 @@ if __name__ == '__main__': try: frac = float(term) if frac > 1 or frac < 0: - print "Fraction argument mus be in [0, 1]" + print >> self.stream, "Fraction argument must be in [0, 1]" continue processed.append(frac) continue @@ -559,93 +573,93 @@ if __name__ == '__main__': if self.stats: getattr(self.stats, fn)(*processed) else: - print "No statistics object is loaded." + print >> self.stream, "No statistics object is loaded." return 0 def generic_help(self): - print "Arguments may be:" - print "* An integer maximum number of entries to print." - print "* A decimal fractional number between 0 and 1, controlling" - print " what fraction of selected entries to print." - print "* A regular expression; only entries with function names" - print " that match it are printed." + print >> self.stream, "Arguments may be:" + print >> self.stream, "* An integer maximum number of entries to print." + print >> self.stream, "* A decimal fractional number between 0 and 1, controlling" + print >> self.stream, " what fraction of selected entries to print." + print >> self.stream, "* A regular expression; only entries with function names" + print >> self.stream, " that match it are printed." def do_add(self, line): self.stats.add(line) return 0 def help_add(self): - print "Add profile info from given file to current statistics object." + print >> self.stream, "Add profile info from given file to current statistics object." def do_callees(self, line): return self.generic('print_callees', line) def help_callees(self): - print "Print callees statistics from the current stat object." + print >> self.stream, "Print callees statistics from the current stat object." self.generic_help() def do_callers(self, line): return self.generic('print_callers', line) def help_callers(self): - print "Print callers statistics from the current stat object." + print >> self.stream, "Print callers statistics from the current stat object." self.generic_help() def do_EOF(self, line): - print "" + print >> self.stream, "" return 1 def help_EOF(self): - print "Leave the profile brower." + print >> self.stream, "Leave the profile brower." def do_quit(self, line): return 1 def help_quit(self): - print "Leave the profile brower." + print >> self.stream, "Leave the profile brower." def do_read(self, line): if line: try: self.stats = Stats(line) except IOError, args: - print args[1] + print >> self.stream, args[1] return self.prompt = line + "% " elif len(self.prompt) > 2: line = self.prompt[-2:] else: - print "No statistics object is current -- cannot reload." + print >> self.stream, "No statistics object is current -- cannot reload." return 0 def help_read(self): - print "Read in profile data from a specified file." + print >> self.stream, "Read in profile data from a specified file." def do_reverse(self, line): self.stats.reverse_order() return 0 def help_reverse(self): - print "Reverse the sort order of the profiling report." + print >> self.stream, "Reverse the sort order of the profiling report." def do_sort(self, line): abbrevs = self.stats.get_sort_arg_defs() if line and not filter(lambda x,a=abbrevs: x not in a,line.split()): self.stats.sort_stats(*line.split()) else: - print "Valid sort keys (unique prefixes are accepted):" + print >> self.stream, "Valid sort keys (unique prefixes are accepted):" for (key, value) in Stats.sort_arg_dict_default.iteritems(): - print "%s -- %s" % (key, value[1]) + print >> self.stream, "%s -- %s" % (key, value[1]) return 0 def help_sort(self): - print "Sort profile data according to specified keys." - print "(Typing `sort' without arguments lists valid keys.)" + print >> self.stream, "Sort profile data according to specified keys." + print >> self.stream, "(Typing `sort' without arguments lists valid keys.)" def complete_sort(self, text, *args): return [a for a in Stats.sort_arg_dict_default if a.startswith(text)] def do_stats(self, line): return self.generic('print_stats', line) def help_stats(self): - print "Print statistics from the current stat object." + print >> self.stream, "Print statistics from the current stat object." self.generic_help() def do_strip(self, line): self.stats.strip_dirs() return 0 def help_strip(self): - print "Strip leading path information from filenames in the report." + print >> self.stream, "Strip leading path information from filenames in the report." def postcmd(self, stop, line): if stop: @@ -653,14 +667,14 @@ if __name__ == '__main__': return None import sys - print "Welcome to the profile statistics browser." + print >> self.stream, "Welcome to the profile statistics browser." if len(sys.argv) > 1: initprofile = sys.argv[1] else: initprofile = None try: ProfileBrowser(initprofile).cmdloop() - print "Goodbye." + print >> self.stream, "Goodbye." except KeyboardInterrupt: pass diff --git a/Misc/NEWS b/Misc/NEWS index 0cda340f37c..03dc27ca4fe 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -120,6 +120,9 @@ Library - Fix exception when doing glob.glob('anything*/') +- The pstats.Stats class accepts an optional stream keyword argument to + direct output to an alternate file-like object. + Build -----