http://scons.tigris.org/issues/show_bug.cgi?id=2329
[scons.git] / src / engine / SCons / Debug.py
1 """SCons.Debug
2
3 Code for debugging SCons internal things.  Not everything here is
4 guaranteed to work all the way back to Python 1.5.2, and shouldn't be
5 needed by most users.
6
7 """
8
9 #
10 # __COPYRIGHT__
11 #
12 # Permission is hereby granted, free of charge, to any person obtaining
13 # a copy of this software and associated documentation files (the
14 # "Software"), to deal in the Software without restriction, including
15 # without limitation the rights to use, copy, modify, merge, publish,
16 # distribute, sublicense, and/or sell copies of the Software, and to
17 # permit persons to whom the Software is furnished to do so, subject to
18 # the following conditions:
19 #
20 # The above copyright notice and this permission notice shall be included
21 # in all copies or substantial portions of the Software.
22 #
23 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
24 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
25 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 #
31
32 __revision__ = "__FILE__ __REVISION__ __DATE__ __DEVELOPER__"
33
34 import os
35 import sys
36 import time
37
38 # Recipe 14.10 from the Python Cookbook.
39 try:
40     import weakref
41 except ImportError:
42     def logInstanceCreation(instance, name=None):
43         pass
44 else:
45     def logInstanceCreation(instance, name=None):
46         if name is None:
47             name = instance.__class__.__name__
48         if name not in tracked_classes:
49             tracked_classes[name] = []
50         tracked_classes[name].append(weakref.ref(instance))
51
52
53
54 tracked_classes = {}
55
56 def string_to_classes(s):
57     if s == '*':
58         return sorted(tracked_classes.keys())
59     else:
60         return s.split()
61
62 def fetchLoggedInstances(classes="*"):
63     classnames = string_to_classes(classes)
64     return [(cn, len(tracked_classes[cn])) for cn in classnames]
65   
66 def countLoggedInstances(classes, file=sys.stdout):
67     for classname in string_to_classes(classes):
68         file.write("%s: %d\n" % (classname, len(tracked_classes[classname])))
69
70 def listLoggedInstances(classes, file=sys.stdout):
71     for classname in string_to_classes(classes):
72         file.write('\n%s:\n' % classname)
73         for ref in tracked_classes[classname]:
74             obj = ref()
75             if obj is not None:
76                 file.write('    %s\n' % repr(obj))
77
78 def dumpLoggedInstances(classes, file=sys.stdout):
79     for classname in string_to_classes(classes):
80         file.write('\n%s:\n' % classname)
81         for ref in tracked_classes[classname]:
82             obj = ref()
83             if obj is not None:
84                 file.write('    %s:\n' % obj)
85                 for key, value in obj.__dict__.items():
86                     file.write('        %20s : %s\n' % (key, value))
87
88
89
90 if sys.platform[:5] == "linux":
91     # Linux doesn't actually support memory usage stats from getrusage().
92     def memory():
93         mstr = open('/proc/self/stat').read()
94         mstr = mstr.split()[22]
95         return int(mstr)
96 else:
97     try:
98         import resource
99     except ImportError:
100         try:
101             import win32process
102             import win32api
103         except ImportError:
104             def memory():
105                 return 0
106         else:
107             def memory():
108                 process_handle = win32api.GetCurrentProcess()
109                 memory_info = win32process.GetProcessMemoryInfo( process_handle )
110                 return memory_info['PeakWorkingSetSize']
111     else:
112         def memory():
113             res = resource.getrusage(resource.RUSAGE_SELF)
114             return res[4]
115
116 # returns caller's stack
117 def caller_stack(*backlist):
118     import traceback
119     if not backlist:
120         backlist = [0]
121     result = []
122     for back in backlist:
123         tb = traceback.extract_stack(limit=3+back)
124         key = tb[0][:3]
125         result.append('%s:%d(%s)' % func_shorten(key))
126     return result
127
128 caller_bases = {}
129 caller_dicts = {}
130
131 # trace a caller's stack
132 def caller_trace(back=0):
133     import traceback
134     tb = traceback.extract_stack(limit=3+back)
135     tb.reverse()
136     callee = tb[1][:3]
137     caller_bases[callee] = caller_bases.get(callee, 0) + 1
138     for caller in tb[2:]:
139         caller = callee + caller[:3]
140         try:
141             entry = caller_dicts[callee]
142         except KeyError:
143             caller_dicts[callee] = entry = {}
144         entry[caller] = entry.get(caller, 0) + 1
145         callee = caller
146
147 # print a single caller and its callers, if any
148 def _dump_one_caller(key, file, level=0):
149     leader = '      '*level
150     for v,c in sorted([(-v,c) for c,v in caller_dicts[key].items()]):
151         file.write("%s  %6d %s:%d(%s)\n" % ((leader,-v) + func_shorten(c[-3:])))
152         if c in caller_dicts:
153             _dump_one_caller(c, file, level+1)
154
155 # print each call tree
156 def dump_caller_counts(file=sys.stdout):
157     for k in sorted(caller_bases.keys()):
158         file.write("Callers of %s:%d(%s), %d calls:\n"
159                     % (func_shorten(k) + (caller_bases[k],)))
160         _dump_one_caller(k, file)
161
162 shorten_list = [
163     ( '/scons/SCons/',          1),
164     ( '/src/engine/SCons/',     1),
165     ( '/usr/lib/python',        0),
166 ]
167
168 if os.sep != '/':
169     shorten_list = [(t[0].replace('/', os.sep), t[1]) for t in shorten_list]
170
171 def func_shorten(func_tuple):
172     f = func_tuple[0]
173     for t in shorten_list:
174         i = f.find(t[0])
175         if i >= 0:
176             if t[1]:
177                 i = i + len(t[0])
178             return (f[i:],)+func_tuple[1:]
179     return func_tuple
180
181
182 TraceFP = {}
183 if sys.platform == 'win32':
184     TraceDefault = 'con'
185 else:
186     TraceDefault = '/dev/tty'
187
188 TimeStampDefault = None
189 StartTime = time.time()
190 PreviousTime = StartTime
191
192 def Trace(msg, file=None, mode='w', tstamp=None):
193     """Write a trace message to a file.  Whenever a file is specified,
194     it becomes the default for the next call to Trace()."""
195     global TraceDefault
196     global TimeStampDefault
197     global PreviousTime
198     if file is None:
199         file = TraceDefault
200     else:
201         TraceDefault = file
202     if tstamp is None:
203         tstamp = TimeStampDefault
204     else:
205         TimeStampDefault = tstamp
206     try:
207         fp = TraceFP[file]
208     except KeyError:
209         try:
210             fp = TraceFP[file] = open(file, mode)
211         except TypeError:
212             # Assume we were passed an open file pointer.
213             fp = file
214     if tstamp:
215         now = time.time()
216         fp.write('%8.4f %8.4f:  ' % (now - StartTime, now - PreviousTime))
217         PreviousTime = now
218     fp.write(msg)
219     fp.flush()
220
221 # Local Variables:
222 # tab-width:4
223 # indent-tabs-mode:nil
224 # End:
225 # vim: set expandtab tabstop=4 shiftwidth=4: