]>
git.proxmox.com Git - mirror_edk2.git/blob - AppPkg/Applications/Python/Python-2.7.2/Lib/pstats.py
1 """Class for printing reports on profiled python code."""
3 # Class for printing reports on profiled python code. rev 1.0 4/1/94
5 # Based on prior profile module by Sjoerd Mullender...
6 # which was hacked somewhat by: Guido van Rossum
8 # see profile.py for more info.
10 # Copyright 1994, by InfoSeek Corporation, all rights reserved.
11 # Written by James Roskind
13 # Permission to use, copy, modify, and distribute this Python software
14 # and its associated documentation for any purpose (subject to the
15 # restriction in the following sentence) without fee is hereby granted,
16 # provided that the above copyright notice appears in all copies, and
17 # that both that copyright notice and this permission notice appear in
18 # supporting documentation, and that the name of InfoSeek not be used in
19 # advertising or publicity pertaining to distribution of the software
20 # without specific, written prior permission. This permission is
21 # explicitly restricted to the copying and modification of the software
22 # to remain in Python, compiled Python, or other languages (such as C)
23 # wherein the modified or derived code is exclusively imported into a
26 # INFOSEEK CORPORATION DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS
27 # SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
28 # FITNESS. IN NO EVENT SHALL INFOSEEK CORPORATION BE LIABLE FOR ANY
29 # SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
30 # RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
31 # CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
32 # CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
40 from functools
import cmp_to_key
45 """This class is used for creating reports from data generated by the
46 Profile class. It is a "friend" of that class, and imports data either
47 by direct access to members of Profile class, or by reading in a dictionary
48 that was emitted (via marshal) from the Profile class.
50 The big change from the previous Profiler (in terms of raw functionality)
51 is that an "add()" method has been provided to combine Stats from
52 several distinct profile runs. Both the constructor and the add()
53 method now take arbitrarily many file names as arguments.
55 All the print methods now take an argument that indicates how many lines
56 to print. If the arg is a floating point number between 0 and 1.0, then
57 it is taken as a decimal percentage of the available lines to be printed
58 (e.g., .1 means print 10% of all available lines). If it is an integer,
59 it is taken to mean the number of lines of data that you wish to have
62 The sort_stats() method now processes some additional options (i.e., in
63 addition to the old -1, 0, 1, or 2). It takes an arbitrary number of
64 quoted strings to select the sort order. For example sort_stats('time',
65 'name') sorts on the major key of 'internal function time', and on the
66 minor key of 'the name of the function'. Look at the two tables in
67 sort_stats() and get_sort_arg_defs(self) for more examples.
69 All methods return self, so you can string together commands like:
70 Stats('foo', 'goo').strip_dirs().sort_stats('calls').\
71 print_stats(5).print_callers(5)
74 def __init__(self
, *args
, **kwds
):
75 # I can't figure out how to explictly specify a stream keyword arg
77 # def __init__(self, *args, stream=sys.stdout): ...
78 # so I use **kwds and sqauwk if something unexpected is passed in.
79 self
.stream
= sys
.stdout
81 self
.stream
= kwds
["stream"]
86 extras
= ", ".join(["%s=%s" % (k
, kwds
[k
]) for k
in keys
])
87 raise ValueError, "unrecognized keyword args: %s" % extras
97 self
.all_callees
= None # calc only if needed
103 self
.max_name_len
= 0
106 self
.sort_arg_dict
= {}
110 self
.get_top_level_stats()
114 print >> self
.stream
, "Invalid timing data",
115 if self
.files
: print >> self
.stream
, self
.files
[-1],
118 def load_stats(self
, arg
):
119 if not arg
: self
.stats
= {}
120 elif isinstance(arg
, basestring
):
122 self
.stats
= marshal
.load(f
)
125 file_stats
= os
.stat(arg
)
126 arg
= time
.ctime(file_stats
.st_mtime
) + " " + arg
127 except: # in case this is not unix
130 elif hasattr(arg
, 'create_stats'):
132 self
.stats
= arg
.stats
135 raise TypeError, "Cannot create or construct a %r object from '%r''" % (
139 def get_top_level_stats(self
):
140 for func
, (cc
, nc
, tt
, ct
, callers
) in self
.stats
.items():
141 self
.total_calls
+= nc
142 self
.prim_calls
+= cc
144 if ("jprofile", 0, "profiler") in callers
:
145 self
.top_level
[func
] = None
146 if len(func_std_string(func
)) > self
.max_name_len
:
147 self
.max_name_len
= len(func_std_string(func
))
149 def add(self
, *arg_list
):
150 if not arg_list
: return self
151 if len(arg_list
) > 1: self
.add(*arg_list
[1:])
153 if type(self
) != type(other
) or self
.__class
__ != other
.__class
__:
155 self
.files
+= other
.files
156 self
.total_calls
+= other
.total_calls
157 self
.prim_calls
+= other
.prim_calls
158 self
.total_tt
+= other
.total_tt
159 for func
in other
.top_level
:
160 self
.top_level
[func
] = None
162 if self
.max_name_len
< other
.max_name_len
:
163 self
.max_name_len
= other
.max_name_len
167 for func
, stat
in other
.stats
.iteritems():
168 if func
in self
.stats
:
169 old_func_stat
= self
.stats
[func
]
171 old_func_stat
= (0, 0, 0, 0, {},)
172 self
.stats
[func
] = add_func_stats(old_func_stat
, stat
)
175 def dump_stats(self
, filename
):
176 """Write the profile data to a file we know how to load back."""
177 f
= file(filename
, 'wb')
179 marshal
.dump(self
.stats
, f
)
183 # list the tuple indices and directions for sorting,
184 # along with some printable description
185 sort_arg_dict_default
= {
186 "calls" : (((1,-1), ), "call count"),
187 "cumulative": (((3,-1), ), "cumulative time"),
188 "file" : (((4, 1), ), "file name"),
189 "line" : (((5, 1), ), "line number"),
190 "module" : (((4, 1), ), "file name"),
191 "name" : (((6, 1), ), "function name"),
192 "nfl" : (((6, 1),(4, 1),(5, 1),), "name/file/line"),
193 "pcalls" : (((0,-1), ), "call count"),
194 "stdname" : (((7, 1), ), "standard name"),
195 "time" : (((2,-1), ), "internal time"),
198 def get_sort_arg_defs(self
):
199 """Expand all abbreviations that are unique."""
200 if not self
.sort_arg_dict
:
201 self
.sort_arg_dict
= dict = {}
203 for word
, tup
in self
.sort_arg_dict_default
.iteritems():
209 bad_list
[fragment
] = 0
212 fragment
= fragment
[:-1]
213 for word
in bad_list
:
215 return self
.sort_arg_dict
217 def sort_stats(self
, *field
):
221 if len(field
) == 1 and isinstance(field
[0], (int, long)):
222 # Be compatible with old profiler
223 field
= [ {-1: "stdname",
226 2: "cumulative"}[field
[0]] ]
228 sort_arg_defs
= self
.get_sort_arg_defs()
233 sort_tuple
= sort_tuple
+ sort_arg_defs
[word
][0]
234 self
.sort_type
+= connector
+ sort_arg_defs
[word
][1]
238 for func
, (cc
, nc
, tt
, ct
, callers
) in self
.stats
.iteritems():
239 stats_list
.append((cc
, nc
, tt
, ct
) + func
+
240 (func_std_string(func
), func
))
242 stats_list
.sort(key
=cmp_to_key(TupleComp(sort_tuple
).compare
))
244 self
.fcn_list
= fcn_list
= []
245 for tuple in stats_list
:
246 fcn_list
.append(tuple[-1])
249 def reverse_order(self
):
251 self
.fcn_list
.reverse()
254 def strip_dirs(self
):
255 oldstats
= self
.stats
256 self
.stats
= newstats
= {}
258 for func
, (cc
, nc
, tt
, ct
, callers
) in oldstats
.iteritems():
259 newfunc
= func_strip_path(func
)
260 if len(func_std_string(newfunc
)) > max_name_len
:
261 max_name_len
= len(func_std_string(newfunc
))
263 for func2
, caller
in callers
.iteritems():
264 newcallers
[func_strip_path(func2
)] = caller
266 if newfunc
in newstats
:
267 newstats
[newfunc
] = add_func_stats(
269 (cc
, nc
, tt
, ct
, newcallers
))
271 newstats
[newfunc
] = (cc
, nc
, tt
, ct
, newcallers
)
272 old_top
= self
.top_level
273 self
.top_level
= new_top
= {}
275 new_top
[func_strip_path(func
)] = None
277 self
.max_name_len
= max_name_len
280 self
.all_callees
= None
283 def calc_callees(self
):
284 if self
.all_callees
: return
285 self
.all_callees
= all_callees
= {}
286 for func
, (cc
, nc
, tt
, ct
, callers
) in self
.stats
.iteritems():
287 if not func
in all_callees
:
288 all_callees
[func
] = {}
289 for func2
, caller
in callers
.iteritems():
290 if not func2
in all_callees
:
291 all_callees
[func2
] = {}
292 all_callees
[func2
][func
] = caller
295 #******************************************************************
296 # The following functions support actual printing of reports
297 #******************************************************************
299 # Optional "amount" is either a line count, or a percentage of lines.
301 def eval_print_amount(self
, sel
, list, msg
):
303 if isinstance(sel
, basestring
):
305 rex
= re
.compile(sel
)
307 msg
+= " <Invalid regular expression %r>\n" % sel
311 if rex
.search(func_std_string(func
)):
312 new_list
.append(func
)
315 if isinstance(sel
, float) and 0.0 <= sel
< 1.0:
316 count
= int(count
* sel
+ .5)
317 new_list
= list[:count
]
318 elif isinstance(sel
, (int, long)) and 0 <= sel
< count
:
320 new_list
= list[:count
]
321 if len(list) != len(new_list
):
322 msg
+= " List reduced from %r to %r due to restriction <%r>\n" % (
323 len(list), len(new_list
), sel
)
327 def get_print_list(self
, sel_list
):
328 width
= self
.max_name_len
330 stat_list
= self
.fcn_list
[:]
331 msg
= " Ordered by: " + self
.sort_type
+ '\n'
333 stat_list
= self
.stats
.keys()
334 msg
= " Random listing order was used\n"
336 for selection
in sel_list
:
337 stat_list
, msg
= self
.eval_print_amount(selection
, stat_list
, msg
)
339 count
= len(stat_list
)
343 print >> self
.stream
, msg
344 if count
< len(self
.stats
):
346 for func
in stat_list
:
347 if len(func_std_string(func
)) > width
:
348 width
= len(func_std_string(func
))
349 return width
+2, stat_list
351 def print_stats(self
, *amount
):
352 for filename
in self
.files
:
353 print >> self
.stream
, filename
354 if self
.files
: print >> self
.stream
356 for func
in self
.top_level
:
357 print >> self
.stream
, indent
, func_get_function_name(func
)
359 print >> self
.stream
, indent
, self
.total_calls
, "function calls",
360 if self
.total_calls
!= self
.prim_calls
:
361 print >> self
.stream
, "(%d primitive calls)" % self
.prim_calls
,
362 print >> self
.stream
, "in %.3f seconds" % self
.total_tt
364 width
, list = self
.get_print_list(amount
)
368 self
.print_line(func
)
373 def print_callees(self
, *amount
):
374 width
, list = self
.get_print_list(amount
)
378 self
.print_call_heading(width
, "called...")
380 if func
in self
.all_callees
:
381 self
.print_call_line(width
, func
, self
.all_callees
[func
])
383 self
.print_call_line(width
, func
, {})
388 def print_callers(self
, *amount
):
389 width
, list = self
.get_print_list(amount
)
391 self
.print_call_heading(width
, "was called by...")
393 cc
, nc
, tt
, ct
, callers
= self
.stats
[func
]
394 self
.print_call_line(width
, func
, callers
, "<-")
399 def print_call_heading(self
, name_size
, column_title
):
400 print >> self
.stream
, "Function ".ljust(name_size
) + column_title
401 # print sub-header only if we have new-style callers
403 for cc
, nc
, tt
, ct
, callers
in self
.stats
.itervalues():
405 value
= callers
.itervalues().next()
406 subheader
= isinstance(value
, tuple)
409 print >> self
.stream
, " "*name_size
+ " ncalls tottime cumtime"
411 def print_call_line(self
, name_size
, source
, call_dict
, arrow
="->"):
412 print >> self
.stream
, func_std_string(source
).ljust(name_size
) + arrow
,
416 clist
= call_dict
.keys()
420 name
= func_std_string(func
)
421 value
= call_dict
[func
]
422 if isinstance(value
, tuple):
423 nc
, cc
, tt
, ct
= value
425 substats
= '%d/%d' % (nc
, cc
)
427 substats
= '%d' % (nc
,)
428 substats
= '%s %s %s %s' % (substats
.rjust(7+2*len(indent
)),
429 f8(tt
), f8(ct
), name
)
430 left_width
= name_size
+ 1
432 substats
= '%s(%r) %s' % (name
, value
, f8(self
.stats
[func
][3]))
433 left_width
= name_size
+ 3
434 print >> self
.stream
, indent
*left_width
+ substats
437 def print_title(self
):
438 print >> self
.stream
, ' ncalls tottime percall cumtime percall',
439 print >> self
.stream
, 'filename:lineno(function)'
441 def print_line(self
, func
): # hack : should print percentages
442 cc
, nc
, tt
, ct
, callers
= self
.stats
[func
]
445 c
= c
+ '/' + str(cc
)
446 print >> self
.stream
, c
.rjust(9),
447 print >> self
.stream
, f8(tt
),
449 print >> self
.stream
, ' '*8,
451 print >> self
.stream
, f8(float(tt
)/nc
),
452 print >> self
.stream
, f8(ct
),
454 print >> self
.stream
, ' '*8,
456 print >> self
.stream
, f8(float(ct
)/cc
),
457 print >> self
.stream
, func_std_string(func
)
460 """This class provides a generic function for comparing any two tuples.
461 Each instance records a list of tuple-indices (from most significant
462 to least significant), and sort direction (ascending or decending) for
463 each tuple-index. The compare functions can then be used as the function
464 argument to the system sort() function when a list of tuples need to be
465 sorted in the instances order."""
467 def __init__(self
, comp_select_list
):
468 self
.comp_select_list
= comp_select_list
470 def compare (self
, left
, right
):
471 for index
, direction
in self
.comp_select_list
:
480 #**************************************************************************
481 # func_name is a triple (file:string, line:int, name:string)
483 def func_strip_path(func_name
):
484 filename
, line
, name
= func_name
485 return os
.path
.basename(filename
), line
, name
487 def func_get_function_name(func
):
490 def func_std_string(func_name
): # match what old profile produced
491 if func_name
[:2] == ('~', 0):
492 # special case for built-in functions
494 if name
.startswith('<') and name
.endswith('>'):
495 return '{%s}' % name
[1:-1]
499 return "%s:%d(%s)" % func_name
501 #**************************************************************************
502 # The following functions combine statists for pairs functions.
503 # The bulk of the processing involves correctly handling "call" lists,
504 # such as callers and callees.
505 #**************************************************************************
507 def add_func_stats(target
, source
):
508 """Add together all the stats for two profile entries."""
509 cc
, nc
, tt
, ct
, callers
= source
510 t_cc
, t_nc
, t_tt
, t_ct
, t_callers
= target
511 return (cc
+t_cc
, nc
+t_nc
, tt
+t_tt
, ct
+t_ct
,
512 add_callers(t_callers
, callers
))
514 def add_callers(target
, source
):
515 """Combine two caller lists in a single list."""
517 for func
, caller
in target
.iteritems():
518 new_callers
[func
] = caller
519 for func
, caller
in source
.iteritems():
520 if func
in new_callers
:
521 if isinstance(caller
, tuple):
522 # format used by cProfile
523 new_callers
[func
] = tuple([i
[0] + i
[1] for i
in
524 zip(caller
, new_callers
[func
])])
526 # format used by profile
527 new_callers
[func
] += caller
529 new_callers
[func
] = caller
532 def count_calls(callers
):
533 """Sum the caller statistics to get total number of calls received."""
535 for calls
in callers
.itervalues():
539 #**************************************************************************
540 # The following functions support printing of reports
541 #**************************************************************************
546 #**************************************************************************
547 # Statistics browser added by ESR, April 2001
548 #**************************************************************************
550 if __name__
== '__main__':
557 class ProfileBrowser(cmd
.Cmd
):
558 def __init__(self
, profile
=None):
559 cmd
.Cmd
.__init
__(self
)
562 self
.stream
= sys
.stdout
563 if profile
is not None:
564 self
.do_read(profile
)
566 def generic(self
, fn
, line
):
571 processed
.append(int(term
))
577 if frac
> 1 or frac
< 0:
578 print >> self
.stream
, "Fraction argument must be in [0, 1]"
580 processed
.append(frac
)
584 processed
.append(term
)
586 getattr(self
.stats
, fn
)(*processed
)
588 print >> self
.stream
, "No statistics object is loaded."
590 def generic_help(self
):
591 print >> self
.stream
, "Arguments may be:"
592 print >> self
.stream
, "* An integer maximum number of entries to print."
593 print >> self
.stream
, "* A decimal fractional number between 0 and 1, controlling"
594 print >> self
.stream
, " what fraction of selected entries to print."
595 print >> self
.stream
, "* A regular expression; only entries with function names"
596 print >> self
.stream
, " that match it are printed."
598 def do_add(self
, line
):
602 print >> self
.stream
, "No statistics object is loaded."
605 print >> self
.stream
, "Add profile info from given file to current statistics object."
607 def do_callees(self
, line
):
608 return self
.generic('print_callees', line
)
609 def help_callees(self
):
610 print >> self
.stream
, "Print callees statistics from the current stat object."
613 def do_callers(self
, line
):
614 return self
.generic('print_callers', line
)
615 def help_callers(self
):
616 print >> self
.stream
, "Print callers statistics from the current stat object."
619 def do_EOF(self
, line
):
620 print >> self
.stream
, ""
623 print >> self
.stream
, "Leave the profile brower."
625 def do_quit(self
, line
):
628 print >> self
.stream
, "Leave the profile brower."
630 def do_read(self
, line
):
633 self
.stats
= Stats(line
)
634 except IOError, args
:
635 print >> self
.stream
, args
[1]
637 except Exception as err
:
638 print >> self
.stream
, err
.__class
__.__name
__ + ':', err
640 self
.prompt
= line
+ "% "
641 elif len(self
.prompt
) > 2:
642 line
= self
.prompt
[:-2]
645 print >> self
.stream
, "No statistics object is current -- cannot reload."
648 print >> self
.stream
, "Read in profile data from a specified file."
649 print >> self
.stream
, "Without argument, reload the current file."
651 def do_reverse(self
, line
):
653 self
.stats
.reverse_order()
655 print >> self
.stream
, "No statistics object is loaded."
657 def help_reverse(self
):
658 print >> self
.stream
, "Reverse the sort order of the profiling report."
660 def do_sort(self
, line
):
662 print >> self
.stream
, "No statistics object is loaded."
664 abbrevs
= self
.stats
.get_sort_arg_defs()
665 if line
and all((x
in abbrevs
) for x
in line
.split()):
666 self
.stats
.sort_stats(*line
.split())
668 print >> self
.stream
, "Valid sort keys (unique prefixes are accepted):"
669 for (key
, value
) in Stats
.sort_arg_dict_default
.iteritems():
670 print >> self
.stream
, "%s -- %s" % (key
, value
[1])
673 print >> self
.stream
, "Sort profile data according to specified keys."
674 print >> self
.stream
, "(Typing `sort' without arguments lists valid keys.)"
675 def complete_sort(self
, text
, *args
):
676 return [a
for a
in Stats
.sort_arg_dict_default
if a
.startswith(text
)]
678 def do_stats(self
, line
):
679 return self
.generic('print_stats', line
)
680 def help_stats(self
):
681 print >> self
.stream
, "Print statistics from the current stat object."
684 def do_strip(self
, line
):
686 self
.stats
.strip_dirs()
688 print >> self
.stream
, "No statistics object is loaded."
689 def help_strip(self
):
690 print >> self
.stream
, "Strip leading path information from filenames in the report."
693 print >> self
.stream
, "Show help for a given command."
695 def postcmd(self
, stop
, line
):
701 if len(sys
.argv
) > 1:
702 initprofile
= sys
.argv
[1]
706 browser
= ProfileBrowser(initprofile
)
707 print >> browser
.stream
, "Welcome to the profile statistics browser."
709 print >> browser
.stream
, "Goodbye."
710 except KeyboardInterrupt: