]> git.proxmox.com Git - mirror_edk2.git/blob - AppPkg/Applications/Python/Python-2.7.2/Lib/pstats.py
EmbeddedPkg: Extend NvVarStoreFormattedLib LIBRARY_CLASS
[mirror_edk2.git] / AppPkg / Applications / Python / Python-2.7.2 / Lib / pstats.py
1 """Class for printing reports on profiled python code."""
2
3 # Class for printing reports on profiled python code. rev 1.0 4/1/94
4 #
5 # Based on prior profile module by Sjoerd Mullender...
6 # which was hacked somewhat by: Guido van Rossum
7 #
8 # see profile.py for more info.
9
10 # Copyright 1994, by InfoSeek Corporation, all rights reserved.
11 # Written by James Roskind
12 #
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
24 # Python module.
25 #
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.
33
34
35 import sys
36 import os
37 import time
38 import marshal
39 import re
40 from functools import cmp_to_key
41
42 __all__ = ["Stats"]
43
44 class Stats:
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.
49
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.
54
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
60 printed.
61
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.
68
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)
72 """
73
74 def __init__(self, *args, **kwds):
75 # I can't figure out how to explictly specify a stream keyword arg
76 # with *args:
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
80 if "stream" in kwds:
81 self.stream = kwds["stream"]
82 del kwds["stream"]
83 if kwds:
84 keys = kwds.keys()
85 keys.sort()
86 extras = ", ".join(["%s=%s" % (k, kwds[k]) for k in keys])
87 raise ValueError, "unrecognized keyword args: %s" % extras
88 if not len(args):
89 arg = None
90 else:
91 arg = args[0]
92 args = args[1:]
93 self.init(arg)
94 self.add(*args)
95
96 def init(self, arg):
97 self.all_callees = None # calc only if needed
98 self.files = []
99 self.fcn_list = None
100 self.total_tt = 0
101 self.total_calls = 0
102 self.prim_calls = 0
103 self.max_name_len = 0
104 self.top_level = {}
105 self.stats = {}
106 self.sort_arg_dict = {}
107 self.load_stats(arg)
108 trouble = 1
109 try:
110 self.get_top_level_stats()
111 trouble = 0
112 finally:
113 if trouble:
114 print >> self.stream, "Invalid timing data",
115 if self.files: print >> self.stream, self.files[-1],
116 print >> self.stream
117
118 def load_stats(self, arg):
119 if not arg: self.stats = {}
120 elif isinstance(arg, basestring):
121 f = open(arg, 'rb')
122 self.stats = marshal.load(f)
123 f.close()
124 try:
125 file_stats = os.stat(arg)
126 arg = time.ctime(file_stats.st_mtime) + " " + arg
127 except: # in case this is not unix
128 pass
129 self.files = [ arg ]
130 elif hasattr(arg, 'create_stats'):
131 arg.create_stats()
132 self.stats = arg.stats
133 arg.stats = {}
134 if not self.stats:
135 raise TypeError, "Cannot create or construct a %r object from '%r''" % (
136 self.__class__, arg)
137 return
138
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
143 self.total_tt += tt
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))
148
149 def add(self, *arg_list):
150 if not arg_list: return self
151 if len(arg_list) > 1: self.add(*arg_list[1:])
152 other = arg_list[0]
153 if type(self) != type(other) or self.__class__ != other.__class__:
154 other = Stats(other)
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
161
162 if self.max_name_len < other.max_name_len:
163 self.max_name_len = other.max_name_len
164
165 self.fcn_list = None
166
167 for func, stat in other.stats.iteritems():
168 if func in self.stats:
169 old_func_stat = self.stats[func]
170 else:
171 old_func_stat = (0, 0, 0, 0, {},)
172 self.stats[func] = add_func_stats(old_func_stat, stat)
173 return self
174
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')
178 try:
179 marshal.dump(self.stats, f)
180 finally:
181 f.close()
182
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"),
196 }
197
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 = {}
202 bad_list = {}
203 for word, tup in self.sort_arg_dict_default.iteritems():
204 fragment = word
205 while fragment:
206 if not fragment:
207 break
208 if fragment in dict:
209 bad_list[fragment] = 0
210 break
211 dict[fragment] = tup
212 fragment = fragment[:-1]
213 for word in bad_list:
214 del dict[word]
215 return self.sort_arg_dict
216
217 def sort_stats(self, *field):
218 if not field:
219 self.fcn_list = 0
220 return self
221 if len(field) == 1 and isinstance(field[0], (int, long)):
222 # Be compatible with old profiler
223 field = [ {-1: "stdname",
224 0: "calls",
225 1: "time",
226 2: "cumulative"}[field[0]] ]
227
228 sort_arg_defs = self.get_sort_arg_defs()
229 sort_tuple = ()
230 self.sort_type = ""
231 connector = ""
232 for word in field:
233 sort_tuple = sort_tuple + sort_arg_defs[word][0]
234 self.sort_type += connector + sort_arg_defs[word][1]
235 connector = ", "
236
237 stats_list = []
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))
241
242 stats_list.sort(key=cmp_to_key(TupleComp(sort_tuple).compare))
243
244 self.fcn_list = fcn_list = []
245 for tuple in stats_list:
246 fcn_list.append(tuple[-1])
247 return self
248
249 def reverse_order(self):
250 if self.fcn_list:
251 self.fcn_list.reverse()
252 return self
253
254 def strip_dirs(self):
255 oldstats = self.stats
256 self.stats = newstats = {}
257 max_name_len = 0
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))
262 newcallers = {}
263 for func2, caller in callers.iteritems():
264 newcallers[func_strip_path(func2)] = caller
265
266 if newfunc in newstats:
267 newstats[newfunc] = add_func_stats(
268 newstats[newfunc],
269 (cc, nc, tt, ct, newcallers))
270 else:
271 newstats[newfunc] = (cc, nc, tt, ct, newcallers)
272 old_top = self.top_level
273 self.top_level = new_top = {}
274 for func in old_top:
275 new_top[func_strip_path(func)] = None
276
277 self.max_name_len = max_name_len
278
279 self.fcn_list = None
280 self.all_callees = None
281 return self
282
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
293 return
294
295 #******************************************************************
296 # The following functions support actual printing of reports
297 #******************************************************************
298
299 # Optional "amount" is either a line count, or a percentage of lines.
300
301 def eval_print_amount(self, sel, list, msg):
302 new_list = list
303 if isinstance(sel, basestring):
304 try:
305 rex = re.compile(sel)
306 except re.error:
307 msg += " <Invalid regular expression %r>\n" % sel
308 return new_list, msg
309 new_list = []
310 for func in list:
311 if rex.search(func_std_string(func)):
312 new_list.append(func)
313 else:
314 count = len(list)
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:
319 count = sel
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)
324
325 return new_list, msg
326
327 def get_print_list(self, sel_list):
328 width = self.max_name_len
329 if self.fcn_list:
330 stat_list = self.fcn_list[:]
331 msg = " Ordered by: " + self.sort_type + '\n'
332 else:
333 stat_list = self.stats.keys()
334 msg = " Random listing order was used\n"
335
336 for selection in sel_list:
337 stat_list, msg = self.eval_print_amount(selection, stat_list, msg)
338
339 count = len(stat_list)
340
341 if not stat_list:
342 return 0, stat_list
343 print >> self.stream, msg
344 if count < len(self.stats):
345 width = 0
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
350
351 def print_stats(self, *amount):
352 for filename in self.files:
353 print >> self.stream, filename
354 if self.files: print >> self.stream
355 indent = ' ' * 8
356 for func in self.top_level:
357 print >> self.stream, indent, func_get_function_name(func)
358
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
363 print >> self.stream
364 width, list = self.get_print_list(amount)
365 if list:
366 self.print_title()
367 for func in list:
368 self.print_line(func)
369 print >> self.stream
370 print >> self.stream
371 return self
372
373 def print_callees(self, *amount):
374 width, list = self.get_print_list(amount)
375 if list:
376 self.calc_callees()
377
378 self.print_call_heading(width, "called...")
379 for func in list:
380 if func in self.all_callees:
381 self.print_call_line(width, func, self.all_callees[func])
382 else:
383 self.print_call_line(width, func, {})
384 print >> self.stream
385 print >> self.stream
386 return self
387
388 def print_callers(self, *amount):
389 width, list = self.get_print_list(amount)
390 if list:
391 self.print_call_heading(width, "was called by...")
392 for func in list:
393 cc, nc, tt, ct, callers = self.stats[func]
394 self.print_call_line(width, func, callers, "<-")
395 print >> self.stream
396 print >> self.stream
397 return self
398
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
402 subheader = False
403 for cc, nc, tt, ct, callers in self.stats.itervalues():
404 if callers:
405 value = callers.itervalues().next()
406 subheader = isinstance(value, tuple)
407 break
408 if subheader:
409 print >> self.stream, " "*name_size + " ncalls tottime cumtime"
410
411 def print_call_line(self, name_size, source, call_dict, arrow="->"):
412 print >> self.stream, func_std_string(source).ljust(name_size) + arrow,
413 if not call_dict:
414 print >> self.stream
415 return
416 clist = call_dict.keys()
417 clist.sort()
418 indent = ""
419 for func in clist:
420 name = func_std_string(func)
421 value = call_dict[func]
422 if isinstance(value, tuple):
423 nc, cc, tt, ct = value
424 if nc != cc:
425 substats = '%d/%d' % (nc, cc)
426 else:
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
431 else:
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
435 indent = " "
436
437 def print_title(self):
438 print >> self.stream, ' ncalls tottime percall cumtime percall',
439 print >> self.stream, 'filename:lineno(function)'
440
441 def print_line(self, func): # hack : should print percentages
442 cc, nc, tt, ct, callers = self.stats[func]
443 c = str(nc)
444 if nc != cc:
445 c = c + '/' + str(cc)
446 print >> self.stream, c.rjust(9),
447 print >> self.stream, f8(tt),
448 if nc == 0:
449 print >> self.stream, ' '*8,
450 else:
451 print >> self.stream, f8(float(tt)/nc),
452 print >> self.stream, f8(ct),
453 if cc == 0:
454 print >> self.stream, ' '*8,
455 else:
456 print >> self.stream, f8(float(ct)/cc),
457 print >> self.stream, func_std_string(func)
458
459 class TupleComp:
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."""
466
467 def __init__(self, comp_select_list):
468 self.comp_select_list = comp_select_list
469
470 def compare (self, left, right):
471 for index, direction in self.comp_select_list:
472 l = left[index]
473 r = right[index]
474 if l < r:
475 return -direction
476 if l > r:
477 return direction
478 return 0
479
480 #**************************************************************************
481 # func_name is a triple (file:string, line:int, name:string)
482
483 def func_strip_path(func_name):
484 filename, line, name = func_name
485 return os.path.basename(filename), line, name
486
487 def func_get_function_name(func):
488 return func[2]
489
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
493 name = func_name[2]
494 if name.startswith('<') and name.endswith('>'):
495 return '{%s}' % name[1:-1]
496 else:
497 return name
498 else:
499 return "%s:%d(%s)" % func_name
500
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 #**************************************************************************
506
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))
513
514 def add_callers(target, source):
515 """Combine two caller lists in a single list."""
516 new_callers = {}
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])])
525 else:
526 # format used by profile
527 new_callers[func] += caller
528 else:
529 new_callers[func] = caller
530 return new_callers
531
532 def count_calls(callers):
533 """Sum the caller statistics to get total number of calls received."""
534 nc = 0
535 for calls in callers.itervalues():
536 nc += calls
537 return nc
538
539 #**************************************************************************
540 # The following functions support printing of reports
541 #**************************************************************************
542
543 def f8(x):
544 return "%8.3f" % x
545
546 #**************************************************************************
547 # Statistics browser added by ESR, April 2001
548 #**************************************************************************
549
550 if __name__ == '__main__':
551 import cmd
552 try:
553 import readline
554 except ImportError:
555 pass
556
557 class ProfileBrowser(cmd.Cmd):
558 def __init__(self, profile=None):
559 cmd.Cmd.__init__(self)
560 self.prompt = "% "
561 self.stats = None
562 self.stream = sys.stdout
563 if profile is not None:
564 self.do_read(profile)
565
566 def generic(self, fn, line):
567 args = line.split()
568 processed = []
569 for term in args:
570 try:
571 processed.append(int(term))
572 continue
573 except ValueError:
574 pass
575 try:
576 frac = float(term)
577 if frac > 1 or frac < 0:
578 print >> self.stream, "Fraction argument must be in [0, 1]"
579 continue
580 processed.append(frac)
581 continue
582 except ValueError:
583 pass
584 processed.append(term)
585 if self.stats:
586 getattr(self.stats, fn)(*processed)
587 else:
588 print >> self.stream, "No statistics object is loaded."
589 return 0
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."
597
598 def do_add(self, line):
599 if self.stats:
600 self.stats.add(line)
601 else:
602 print >> self.stream, "No statistics object is loaded."
603 return 0
604 def help_add(self):
605 print >> self.stream, "Add profile info from given file to current statistics object."
606
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."
611 self.generic_help()
612
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."
617 self.generic_help()
618
619 def do_EOF(self, line):
620 print >> self.stream, ""
621 return 1
622 def help_EOF(self):
623 print >> self.stream, "Leave the profile brower."
624
625 def do_quit(self, line):
626 return 1
627 def help_quit(self):
628 print >> self.stream, "Leave the profile brower."
629
630 def do_read(self, line):
631 if line:
632 try:
633 self.stats = Stats(line)
634 except IOError, args:
635 print >> self.stream, args[1]
636 return
637 except Exception as err:
638 print >> self.stream, err.__class__.__name__ + ':', err
639 return
640 self.prompt = line + "% "
641 elif len(self.prompt) > 2:
642 line = self.prompt[:-2]
643 self.do_read(line)
644 else:
645 print >> self.stream, "No statistics object is current -- cannot reload."
646 return 0
647 def help_read(self):
648 print >> self.stream, "Read in profile data from a specified file."
649 print >> self.stream, "Without argument, reload the current file."
650
651 def do_reverse(self, line):
652 if self.stats:
653 self.stats.reverse_order()
654 else:
655 print >> self.stream, "No statistics object is loaded."
656 return 0
657 def help_reverse(self):
658 print >> self.stream, "Reverse the sort order of the profiling report."
659
660 def do_sort(self, line):
661 if not self.stats:
662 print >> self.stream, "No statistics object is loaded."
663 return
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())
667 else:
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])
671 return 0
672 def help_sort(self):
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)]
677
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."
682 self.generic_help()
683
684 def do_strip(self, line):
685 if self.stats:
686 self.stats.strip_dirs()
687 else:
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."
691
692 def help_help(self):
693 print >> self.stream, "Show help for a given command."
694
695 def postcmd(self, stop, line):
696 if stop:
697 return stop
698 return None
699
700 import sys
701 if len(sys.argv) > 1:
702 initprofile = sys.argv[1]
703 else:
704 initprofile = None
705 try:
706 browser = ProfileBrowser(initprofile)
707 print >> browser.stream, "Welcome to the profile statistics browser."
708 browser.cmdloop()
709 print >> browser.stream, "Goodbye."
710 except KeyboardInterrupt:
711 pass
712
713 # That's all, folks.