logging working in NewParallel, but changed to be default. Need to figure out how...
[scons.git] / SCons / Debug.py
blobfa077436cbeba135cfdf6e5a48223333e7580ef4
1 # MIT License
3 # Copyright The SCons Foundation
5 # Permission is hereby granted, free of charge, to any person obtaining
6 # a copy of this software and associated documentation files (the
7 # "Software"), to deal in the Software without restriction, including
8 # without limitation the rights to use, copy, modify, merge, publish,
9 # distribute, sublicense, and/or sell copies of the Software, and to
10 # permit persons to whom the Software is furnished to do so, subject to
11 # the following conditions:
13 # The above copyright notice and this permission notice shall be included
14 # in all copies or substantial portions of the Software.
16 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
17 # KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
18 # WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
24 """Code for debugging SCons internal things.
26 Shouldn't be needed by most users. Quick shortcuts:
28 from SCons.Debug import caller_trace
29 caller_trace()
30 """
32 import atexit
33 import os
34 import sys
35 import time
36 import weakref
37 import inspect
39 # Global variable that gets set to 'True' by the Main script,
40 # when the creation of class instances should get tracked.
41 track_instances = False
42 # List of currently tracked classes
43 tracked_classes = {}
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 if hasattr(instance, '__dict__'):
51 tracked_classes[name].append(weakref.ref(instance))
52 else:
53 # weakref doesn't seem to work when the instance
54 # contains only slots...
55 tracked_classes[name].append(instance)
57 def string_to_classes(s):
58 if s == '*':
59 return sorted(tracked_classes.keys())
60 else:
61 return s.split()
63 def fetchLoggedInstances(classes="*"):
64 classnames = string_to_classes(classes)
65 return [(cn, len(tracked_classes[cn])) for cn in classnames]
67 def countLoggedInstances(classes, file=sys.stdout):
68 for classname in string_to_classes(classes):
69 file.write("%s: %d\n" % (classname, len(tracked_classes[classname])))
71 def listLoggedInstances(classes, file=sys.stdout):
72 for classname in string_to_classes(classes):
73 file.write('\n%s:\n' % classname)
74 for ref in tracked_classes[classname]:
75 if inspect.isclass(ref):
76 obj = ref()
77 else:
78 obj = ref
79 if obj is not None:
80 file.write(' %s\n' % repr(obj))
82 def dumpLoggedInstances(classes, file=sys.stdout):
83 for classname in string_to_classes(classes):
84 file.write('\n%s:\n' % classname)
85 for ref in tracked_classes[classname]:
86 obj = ref()
87 if obj is not None:
88 file.write(' %s:\n' % obj)
89 for key, value in obj.__dict__.items():
90 file.write(' %20s : %s\n' % (key, value))
93 if sys.platform[:5] == "linux":
94 # Linux doesn't actually support memory usage stats from getrusage().
95 def memory():
96 with open('/proc/self/stat') as f:
97 mstr = f.read()
98 mstr = mstr.split()[22]
99 return int(mstr)
100 elif sys.platform[:6] == 'darwin':
101 #TODO really get memory stats for OS X
102 def memory():
103 return 0
104 elif sys.platform == 'win32':
105 from SCons.compat.win32 import get_peak_memory_usage
106 memory = get_peak_memory_usage
107 else:
108 try:
109 import resource
110 except ImportError:
111 def memory():
112 return 0
113 else:
114 def memory():
115 res = resource.getrusage(resource.RUSAGE_SELF)
116 return res[4]
119 def caller_stack():
120 """return caller's stack"""
121 import traceback
122 tb = traceback.extract_stack()
123 # strip itself and the caller from the output
124 tb = tb[:-2]
125 result = []
126 for back in tb:
127 # (filename, line number, function name, text)
128 key = back[:3]
129 result.append('%s:%d(%s)' % func_shorten(key))
130 return result
132 caller_bases = {}
133 caller_dicts = {}
135 def caller_trace(back=0):
137 Trace caller stack and save info into global dicts, which
138 are printed automatically at the end of SCons execution.
140 global caller_bases, caller_dicts
141 import traceback
142 tb = traceback.extract_stack(limit=3+back)
143 tb.reverse()
144 callee = tb[1][:3]
145 caller_bases[callee] = caller_bases.get(callee, 0) + 1
146 for caller in tb[2:]:
147 caller = callee + caller[:3]
148 try:
149 entry = caller_dicts[callee]
150 except KeyError:
151 caller_dicts[callee] = entry = {}
152 entry[caller] = entry.get(caller, 0) + 1
153 callee = caller
155 # print a single caller and its callers, if any
156 def _dump_one_caller(key, file, level=0):
157 leader = ' '*level
158 for v,c in sorted([(-v,c) for c,v in caller_dicts[key].items()]):
159 file.write("%s %6d %s:%d(%s)\n" % ((leader,-v) + func_shorten(c[-3:])))
160 if c in caller_dicts:
161 _dump_one_caller(c, file, level+1)
163 # print each call tree
164 def dump_caller_counts(file=sys.stdout):
165 for k in sorted(caller_bases.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)
170 shorten_list = [
171 ( '/scons/SCons/', 1),
172 ( '/src/engine/SCons/', 1),
173 ( '/usr/lib/python', 0),
176 if os.sep != '/':
177 shorten_list = [(t[0].replace('/', os.sep), t[1]) for t in shorten_list]
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
190 TraceFP = {}
191 if sys.platform == 'win32':
192 TraceDefault = 'con'
193 else:
194 TraceDefault = '/dev/tty'
195 TimeStampDefault = False
196 StartTime = time.perf_counter()
197 PreviousTime = StartTime
199 def Trace(msg, tracefile=None, mode='w', tstamp=False):
200 """Write a trace message.
202 Write messages when debugging which do not interfere with stdout.
203 Useful in tests, which monitor stdout and would break with
204 unexpected output. Trace messages can go to the console (which is
205 opened as a file), or to a disk file; the tracefile argument persists
206 across calls unless overridden.
208 Args:
209 tracefile: file to write trace message to. If omitted,
210 write to the previous trace file (default: console).
211 mode: file open mode (default: 'w')
212 tstamp: write relative timestamps with trace. Outputs time since
213 scons was started, and time since last trace (default: False)
216 global TraceDefault
217 global TimeStampDefault
218 global PreviousTime
220 def trace_cleanup(traceFP):
221 traceFP.close()
223 if tracefile is None:
224 tracefile = TraceDefault
225 else:
226 TraceDefault = tracefile
227 if not tstamp:
228 tstamp = TimeStampDefault
229 else:
230 TimeStampDefault = tstamp
231 try:
232 fp = TraceFP[tracefile]
233 except KeyError:
234 try:
235 fp = TraceFP[tracefile] = open(tracefile, mode)
236 atexit.register(trace_cleanup, fp)
237 except TypeError:
238 # Assume we were passed an open file pointer.
239 fp = tracefile
240 if tstamp:
241 now = time.perf_counter()
242 fp.write('%8.4f %8.4f: ' % (now - StartTime, now - PreviousTime))
243 PreviousTime = now
244 fp.write(msg)
245 fp.flush()
247 # Local Variables:
248 # tab-width:4
249 # indent-tabs-mode:nil
250 # End:
251 # vim: set expandtab tabstop=4 shiftwidth=4: