a6c0cb55dd589400d0b417ed862ece822712ce18
[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         c = tracked_classes.keys()
59         c.sort()
60         return c
61     else:
62         return s.split()
63
64 def fetchLoggedInstances(classes="*"):
65     classnames = string_to_classes(classes)
66     return [(cn, len(tracked_classes[cn])) for cn in classnames]
67   
68 def countLoggedInstances(classes, file=sys.stdout):
69     for classname in string_to_classes(classes):
70         file.write("%s: %d\n" % (classname, len(tracked_classes[classname])))
71
72 def listLoggedInstances(classes, file=sys.stdout):
73     for classname in string_to_classes(classes):
74         file.write('\n%s:\n' % classname)
75         for ref in tracked_classes[classname]:
76             obj = ref()
77             if obj is not None:
78                 file.write('    %s\n' % repr(obj))
79
80 def dumpLoggedInstances(classes, file=sys.stdout):
81     for classname in string_to_classes(classes):
82         file.write('\n%s:\n' % classname)
83         for ref in tracked_classes[classname]:
84             obj = ref()
85             if obj is not None:
86                 file.write('    %s:\n' % obj)
87                 for key, value in obj.__dict__.items():
88                     file.write('        %20s : %s\n' % (key, value))
89
90
91
92 if sys.platform[:5] == "linux":
93     # Linux doesn't actually support memory usage stats from getrusage().
94     def memory():
95         mstr = open('/proc/self/stat').read()
96         mstr = mstr.split()[22]
97         return int(mstr)
98 else:
99     try:
100         import resource
101     except ImportError:
102         try:
103             import win32process
104             import win32api
105         except ImportError:
106             def memory():
107                 return 0
108         else:
109             def memory():
110                 process_handle = win32api.GetCurrentProcess()
111                 memory_info = win32process.GetProcessMemoryInfo( process_handle )
112                 return memory_info['PeakWorkingSetSize']
113     else:
114         def memory():
115             res = resource.getrusage(resource.RUSAGE_SELF)
116             return res[4]
117
118 # returns caller's stack
119 def caller_stack(*backlist):
120     import traceback
121     if not backlist:
122         backlist = [0]
123     result = []
124     for back in backlist:
125         tb = traceback.extract_stack(limit=3+back)
126         key = tb[0][:3]
127         result.append('%s:%d(%s)' % func_shorten(key))
128     return result
129
130 caller_bases = {}
131 caller_dicts = {}
132
133 # trace a caller's stack
134 def caller_trace(back=0):
135     import traceback
136     tb = traceback.extract_stack(limit=3+back)
137     tb.reverse()
138     callee = tb[1][:3]
139     caller_bases[callee] = caller_bases.get(callee, 0) + 1
140     for caller in tb[2:]:
141         caller = callee + caller[:3]
142         try:
143             entry = caller_dicts[callee]
144         except KeyError:
145             caller_dicts[callee] = entry = {}
146         entry[caller] = entry.get(caller, 0) + 1
147         callee = caller
148
149 # print a single caller and its callers, if any
150 def _dump_one_caller(key, file, level=0):
151     l = []
152     for c,v in caller_dicts[key].items():
153         l.append((-v,c))
154     l.sort()
155     leader = '      '*level
156     for v,c in l:
157         file.write("%s  %6d %s:%d(%s)\n" % ((leader,-v) + func_shorten(c[-3:])))
158         if c in caller_dicts:
159             _dump_one_caller(c, file, level+1)
160
161 # print each call tree
162 def dump_caller_counts(file=sys.stdout):
163     keys = caller_bases.keys()
164     keys.sort()
165     for k in keys:
166         file.write("Callers of %s:%d(%s), %d calls:\n"
167                     % (func_shorten(k) + (caller_bases[k],)))
168         _dump_one_caller(k, file)
169
170 shorten_list = [
171     ( '/scons/SCons/',          1),
172     ( '/src/engine/SCons/',     1),
173     ( '/usr/lib/python',        0),
174 ]
175
176 if os.sep != '/':
177     shorten_list = [(t[0].replace('/', os.sep), t[1]) for t in shorten_list]
178
179 def func_shorten(func_tuple):
180     f = func_tuple[0]
181     for t in shorten_list:
182         i = f.find(t[0])
183         if i >= 0:
184             if t[1]:
185                 i = i + len(t[0])
186             return (f[i:],)+func_tuple[1:]
187     return func_tuple
188
189
190 TraceFP = {}
191 if sys.platform == 'win32':
192     TraceDefault = 'con'
193 else:
194     TraceDefault = '/dev/tty'
195
196 TimeStampDefault = None
197 StartTime = time.time()
198 PreviousTime = StartTime
199
200 def Trace(msg, file=None, mode='w', tstamp=None):
201     """Write a trace message to a file.  Whenever a file is specified,
202     it becomes the default for the next call to Trace()."""
203     global TraceDefault
204     global TimeStampDefault
205     global PreviousTime
206     if file is None:
207         file = TraceDefault
208     else:
209         TraceDefault = file
210     if tstamp is None:
211         tstamp = TimeStampDefault
212     else:
213         TimeStampDefault = tstamp
214     try:
215         fp = TraceFP[file]
216     except KeyError:
217         try:
218             fp = TraceFP[file] = open(file, mode)
219         except TypeError:
220             # Assume we were passed an open file pointer.
221             fp = file
222     if tstamp:
223         now = time.time()
224         fp.write('%8.4f %8.4f:  ' % (now - StartTime, now - PreviousTime))
225         PreviousTime = now
226     fp.write(msg)
227     fp.flush()
228
229 # Local Variables:
230 # tab-width:4
231 # indent-tabs-mode:nil
232 # End:
233 # vim: set expandtab tabstop=4 shiftwidth=4: