1# MIT License 2# 3# Copyright The SCons Foundation 4# 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: 12# 13# The above copyright notice and this permission notice shall be included 14# in all copies or substantial portions of the Software. 15# 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. 23 24"""Code for debugging SCons internal things. 25 26Shouldn't be needed by most users. Quick shortcuts: 27 28from SCons.Debug import caller_trace 29caller_trace() 30""" 31 32import atexit 33import os 34import sys 35import time 36import weakref 37import inspect 38 39# Global variable that gets set to 'True' by the Main script, 40# when the creation of class instances should get tracked. 41track_instances = False 42# List of currently tracked classes 43tracked_classes = {} 44 45def 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) 56 57def string_to_classes(s): 58 if s == '*': 59 return sorted(tracked_classes.keys()) 60 else: 61 return s.split() 62 63def fetchLoggedInstances(classes="*"): 64 classnames = string_to_classes(classes) 65 return [(cn, len(tracked_classes[cn])) for cn in classnames] 66 67def countLoggedInstances(classes, file=sys.stdout): 68 for classname in string_to_classes(classes): 69 file.write("%s: %d\n" % (classname, len(tracked_classes[classname]))) 70 71def 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)) 81 82def 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)) 91 92 93if 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) 100elif sys.platform[:6] == 'darwin': 101 #TODO really get memory stats for OS X 102 def memory(): 103 return 0 104elif sys.platform == 'win32': 105 from SCons.compat.win32 import get_peak_memory_usage 106 memory = get_peak_memory_usage 107else: 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] 117 118 119def 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 131 132caller_bases = {} 133caller_dicts = {} 134 135def caller_trace(back=0): 136 """ 137 Trace caller stack and save info into global dicts, which 138 are printed automatically at the end of SCons execution. 139 """ 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 154 155# print a single caller and its callers, if any 156def _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) 162 163# print each call tree 164def 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) 169 170shorten_list = [ 171 ( '/scons/SCons/', 1), 172 ( '/src/engine/SCons/', 1), 173 ( '/usr/lib/python', 0), 174] 175 176if os.sep != '/': 177 shorten_list = [(t[0].replace('/', os.sep), t[1]) for t in shorten_list] 178 179def 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 190TraceFP = {} 191if sys.platform == 'win32': 192 TraceDefault = 'con' 193else: 194 TraceDefault = '/dev/tty' 195TimeStampDefault = False 196StartTime = time.perf_counter() 197PreviousTime = StartTime 198 199def Trace(msg, tracefile=None, mode='w', tstamp=False): 200 """Write a trace message. 201 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. 207 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) 214 215 """ 216 global TraceDefault 217 global TimeStampDefault 218 global PreviousTime 219 220 def trace_cleanup(traceFP): 221 traceFP.close() 222 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() 246 247# Local Variables: 248# tab-width:4 249# indent-tabs-mode:nil 250# End: 251# vim: set expandtab tabstop=4 shiftwidth=4: 252