[ci skip] update generated files
[scons.git] / bin / caller-tree.py
blobb2eab2820c1f40513f9a3ecaf095881c79cfe129
1 #!/usr/bin/env python
3 # Quick script to process the *summary* output from SCons.Debug.caller()
4 # and print indented calling trees with call counts.
6 # The way to use this is to add something like the following to a function
7 # for which you want information about who calls it and how many times:
9 # from SCons.Debug import caller
10 # caller(0, 1, 2, 3, 4, 5)
12 # Each integer represents how many stack frames back SCons will go
13 # and capture the calling information, so in the above example it will
14 # capture the calls six levels up the stack in a central dictionary.
16 # At the end of any run where SCons.Debug.caller() is used, SCons will
17 # print a summary of the calls and counts that looks like the following:
19 # Callers of Node/__init__.py:629(calc_signature):
20 # 1 Node/__init__.py:683(calc_signature)
21 # Callers of Node/__init__.py:676(gen_binfo):
22 # 6 Node/FS.py:2035(current)
23 # 1 Node/__init__.py:722(get_bsig)
25 # If you cut-and-paste that summary output and feed it to this script
26 # on standard input, it will figure out how these entries hook up and
27 # print a calling tree for each one looking something like:
29 # Node/__init__.py:676(gen_binfo)
30 # Node/FS.py:2035(current) 6
31 # Taskmaster.py:253(make_ready_current) 18
32 # Script/Main.py:201(make_ready) 18
34 # Note that you should *not* look at the call-count numbers in the right
35 # hand column as the actual number of times each line *was called by*
36 # the function on the next line. Rather, it's the *total* number
37 # of times each function was found in the call chain for any of the
38 # calls to SCons.Debug.caller(). If you're looking at more than one
39 # function at the same time, for example, their counts will intermix.
40 # So use this to get a *general* idea of who's calling what, not for
41 # fine-grained performance tuning.
42 import sys
44 class Entry:
45 def __init__(self, file_line_func):
46 self.file_line_func = file_line_func
47 self.called_by = []
48 self.calls = []
50 AllCalls = {}
52 def get_call(flf):
53 try:
54 e = AllCalls[flf]
55 except KeyError:
56 e = AllCalls[flf] = Entry(flf)
57 return e
59 prefix = 'Callers of '
61 c = None
62 for line in sys.stdin.readlines():
63 if line[0] == '#':
64 pass
65 elif line[:len(prefix)] == prefix:
66 c = get_call(line[len(prefix):-2])
67 else:
68 num_calls, flf = line.strip().split()
69 e = get_call(flf)
70 c.called_by.append((e, num_calls))
71 e.calls.append(c)
73 stack = []
75 def print_entry(e, level, calls):
76 print('%-72s%6s' % ((' '*2*level) + e.file_line_func, calls))
77 if e in stack:
78 print((' '*2*(level+1))+'RECURSION')
79 print()
80 elif e.called_by:
81 stack.append(e)
82 for c in e.called_by:
83 print_entry(c[0], level+1, c[1])
84 stack.pop()
85 else:
86 print()
88 for e in [ e for e in list(AllCalls.values()) if not e.calls ]:
89 print_entry(e, 0, '')
91 # Local Variables:
92 # tab-width:4
93 # indent-tabs-mode:nil
94 # End:
95 # vim: set expandtab tabstop=4 shiftwidth=4: