DebugClients/Python3/coverage/collector.py

changeset 29
391dc0bc4ae5
parent 0
de9c2efb9d02
child 3495
fac17a82b431
equal deleted inserted replaced
28:dde24fc7f7ba 29:391dc0bc4ae5
2 2
3 import sys, threading 3 import sys, threading
4 4
5 try: 5 try:
6 # Use the C extension code when we can, for speed. 6 # Use the C extension code when we can, for speed.
7 from tracer import Tracer 7 from .tracer import Tracer
8 except ImportError: 8 except ImportError:
9 # Couldn't import the C extension, maybe it isn't built. 9 # Couldn't import the C extension, maybe it isn't built.
10 10 Tracer = None
11 class Tracer: 11
12 """Python implementation of the raw data tracer.""" 12
13 def __init__(self): 13 class PyTracer(object):
14 self.data = None 14 """Python implementation of the raw data tracer."""
15 self.should_trace = None 15
16 self.should_trace_cache = None 16 # Because of poor implementations of trace-function-manipulating tools,
17 self.cur_filename = None 17 # the Python trace function must be kept very simple. In particular, there
18 self.filename_stack = [] 18 # must be only one function ever set as the trace function, both through
19 # sys.settrace, and as the return value from the trace function. Put
20 # another way, the trace function must always return itself. It cannot
21 # swap in other functions, or return None to avoid tracing a particular
22 # frame.
23 #
24 # The trace manipulator that introduced this restriction is DecoratorTools,
25 # which sets a trace function, and then later restores the pre-existing one
26 # by calling sys.settrace with a function it found in the current frame.
27 #
28 # Systems that use DecoratorTools (or similar trace manipulations) must use
29 # PyTracer to get accurate results. The command-line --timid argument is
30 # used to force the use of this tracer.
31
32 def __init__(self):
33 self.data = None
34 self.should_trace = None
35 self.should_trace_cache = None
36 self.cur_file_data = None
37 self.last_line = 0
38 self.data_stack = []
39 self.last_exc_back = None
40 self.arcs = False
41
42 def _trace(self, frame, event, arg_unused):
43 """The trace function passed to sys.settrace."""
44
45 #print "trace event: %s %r @%d" % (
46 # event, frame.f_code.co_filename, frame.f_lineno)
47
48 if self.last_exc_back:
49 if frame == self.last_exc_back:
50 # Someone forgot a return event.
51 if self.arcs and self.cur_file_data:
52 self.cur_file_data[(self.last_line, -1)] = None
53 self.cur_file_data, self.last_line = self.data_stack.pop()
19 self.last_exc_back = None 54 self.last_exc_back = None
20 55
21 def _global_trace(self, frame, event, arg_unused): 56 if event == 'call':
22 """The trace function passed to sys.settrace.""" 57 # Entering a new function context. Decide if we should trace
23 if event == 'call': 58 # in this file.
24 # Entering a new function context. Decide if we should trace 59 self.data_stack.append((self.cur_file_data, self.last_line))
25 # in this file. 60 filename = frame.f_code.co_filename
26 filename = frame.f_code.co_filename 61 tracename = self.should_trace(filename, frame)
27 tracename = self.should_trace_cache.get(filename) 62 if tracename:
28 if tracename is None: 63 if tracename not in self.data:
29 tracename = self.should_trace(filename, frame) 64 self.data[tracename] = {}
30 self.should_trace_cache[filename] = tracename 65 self.cur_file_data = self.data[tracename]
31 if tracename: 66 else:
32 # We need to trace. Push the current filename on the stack 67 self.cur_file_data = None
33 # and record the new current filename. 68 self.last_line = -1
34 self.filename_stack.append(self.cur_filename) 69 elif event == 'line':
35 self.cur_filename = tracename 70 # Record an executed line.
36 # Use _local_trace for tracing within this function. 71 if self.cur_file_data is not None:
37 return self._local_trace 72 if self.arcs:
73 #print "lin", self.last_line, frame.f_lineno
74 self.cur_file_data[(self.last_line, frame.f_lineno)] = None
38 else: 75 else:
39 # No tracing in this function. 76 #print "lin", frame.f_lineno
40 return None 77 self.cur_file_data[frame.f_lineno] = None
41 return self._global_trace 78 self.last_line = frame.f_lineno
42 79 elif event == 'return':
43 def _local_trace(self, frame, event, arg_unused): 80 if self.arcs and self.cur_file_data:
44 """The trace function used within a function.""" 81 self.cur_file_data[(self.last_line, -1)] = None
45 if self.last_exc_back: 82 # Leaving this function, pop the filename stack.
46 if frame == self.last_exc_back: 83 self.cur_file_data, self.last_line = self.data_stack.pop()
47 # Someone forgot a return event. 84 elif event == 'exception':
48 self.cur_filename = self.filename_stack.pop() 85 #print "exc", self.last_line, frame.f_lineno
49 self.last_exc_back = None 86 self.last_exc_back = frame.f_back
50 87 return self._trace
51 if event == 'line': 88
52 # Record an executed line. 89 def start(self):
53 self.data[(self.cur_filename, frame.f_lineno)] = True 90 """Start this Tracer."""
54 elif event == 'return': 91 sys.settrace(self._trace)
55 # Leaving this function, pop the filename stack. 92
56 self.cur_filename = self.filename_stack.pop() 93 def stop(self):
57 elif event == 'exception': 94 """Stop this Tracer."""
58 self.last_exc_back = frame.f_back 95 sys.settrace(None)
59 return self._local_trace 96
60 97 def get_stats(self):
61 def start(self): 98 """Return a dictionary of statistics, or None."""
62 """Start this Tracer.""" 99 return None
63 sys.settrace(self._global_trace) 100
64 101
65 def stop(self): 102 class Collector(object):
66 """Stop this Tracer."""
67 sys.settrace(None)
68
69
70 class Collector:
71 """Collects trace data. 103 """Collects trace data.
72 104
73 Creates a Tracer object for each thread, since they track stack information. 105 Creates a Tracer object for each thread, since they track stack
74 Each Tracer points to the same shared data, contributing traced data points. 106 information. Each Tracer points to the same shared data, contributing
75 107 traced data points.
108
76 When the Collector is started, it creates a Tracer for the current thread, 109 When the Collector is started, it creates a Tracer for the current thread,
77 and installs a function to create Tracers for each new thread started. 110 and installs a function to create Tracers for each new thread started.
78 When the Collector is stopped, all active Tracers are stopped. 111 When the Collector is stopped, all active Tracers are stopped.
79 112
80 Threads started while the Collector is stopped will never have Tracers 113 Threads started while the Collector is stopped will never have Tracers
81 associated with them. 114 associated with them.
82 115
83 """ 116 """
84 117
85 # The stack of active Collectors. Collectors are added here when started, 118 # The stack of active Collectors. Collectors are added here when started,
86 # and popped when stopped. Collectors on the stack are paused when not 119 # and popped when stopped. Collectors on the stack are paused when not
87 # the top, and resumed when they become the top again. 120 # the top, and resumed when they become the top again.
88 _collectors = [] 121 _collectors = []
89 122
90 def __init__(self, should_trace): 123 def __init__(self, should_trace, timid, branch):
91 """Create a collector. 124 """Create a collector.
92 125
93 `should_trace` is a function, taking a filename, and returning a 126 `should_trace` is a function, taking a filename, and returning a
94 canonicalized filename, or False depending on whether the file should 127 canonicalized filename, or False depending on whether the file should
95 be traced or not. 128 be traced or not.
96 129
130 If `timid` is true, then a slower simpler trace function will be
131 used. This is important for some environments where manipulation of
132 tracing functions make the faster more sophisticated trace function not
133 operate properly.
134
135 If `branch` is true, then branches will be measured. This involves
136 collecting data on which statements followed each other (arcs). Use
137 `get_arc_data` to get the arc data.
138
97 """ 139 """
98 self.should_trace = should_trace 140 self.should_trace = should_trace
141 self.branch = branch
99 self.reset() 142 self.reset()
143
144 if timid:
145 # Being timid: use the simple Python trace function.
146 self._trace_class = PyTracer
147 else:
148 # Being fast: use the C Tracer if it is available, else the Python
149 # trace function.
150 self._trace_class = Tracer or PyTracer
151
152 def __repr__(self):
153 return "<Collector at 0x%x>" % id(self)
154
155 def tracer_name(self):
156 """Return the class name of the tracer we're using."""
157 return self._trace_class.__name__
100 158
101 def reset(self): 159 def reset(self):
102 """Clear collected data, and prepare to collect more.""" 160 """Clear collected data, and prepare to collect more."""
103 # A dictionary with an entry for (Python source file name, line number 161 # A dictionary mapping filenames to dicts with linenumber keys,
104 # in that file) if that line has been executed. 162 # or mapping filenames to dicts with linenumber pairs as keys.
105 self.data = {} 163 self.data = {}
106 164
107 # A cache of the results from should_trace, the decision about whether 165 # A cache of the results from should_trace, the decision about whether
108 # to trace execution in a file. A dict of filename to (filename or 166 # to trace execution in a file. A dict of filename to (filename or
109 # False). 167 # False).
110 self.should_trace_cache = {} 168 self.should_trace_cache = {}
111 169
112 # Our active Tracers. 170 # Our active Tracers.
113 self.tracers = [] 171 self.tracers = []
114 172
115 def _start_tracer(self): 173 def _start_tracer(self):
116 """Start a new Tracer object, and store it in self.tracers.""" 174 """Start a new Tracer object, and store it in self.tracers."""
117 tracer = Tracer() 175 tracer = self._trace_class()
118 tracer.data = self.data 176 tracer.data = self.data
177 tracer.arcs = self.branch
119 tracer.should_trace = self.should_trace 178 tracer.should_trace = self.should_trace
120 tracer.should_trace_cache = self.should_trace_cache 179 tracer.should_trace_cache = self.should_trace_cache
121 tracer.start() 180 tracer.start()
122 self.tracers.append(tracer) 181 self.tracers.append(tracer)
123 182
139 def start(self): 198 def start(self):
140 """Start collecting trace information.""" 199 """Start collecting trace information."""
141 if self._collectors: 200 if self._collectors:
142 self._collectors[-1].pause() 201 self._collectors[-1].pause()
143 self._collectors.append(self) 202 self._collectors.append(self)
203 #print >>sys.stderr, "Started: %r" % self._collectors
144 # Install the tracer on this thread. 204 # Install the tracer on this thread.
145 self._start_tracer() 205 self._start_tracer()
146 # Install our installation tracer in threading, to jump start other 206 # Install our installation tracer in threading, to jump start other
147 # threads. 207 # threads.
148 threading.settrace(self._installation_trace) 208 threading.settrace(self._installation_trace)
149 209
150 def stop(self): 210 def stop(self):
151 """Stop collecting trace information.""" 211 """Stop collecting trace information."""
212 #print >>sys.stderr, "Stopping: %r" % self._collectors
152 assert self._collectors 213 assert self._collectors
153 assert self._collectors[-1] is self 214 assert self._collectors[-1] is self
154 215
155 for tracer in self.tracers: 216 self.pause()
156 tracer.stop()
157 self.tracers = [] 217 self.tracers = []
158 threading.settrace(None) 218
159
160 # Remove this Collector from the stack, and resume the one underneath 219 # Remove this Collector from the stack, and resume the one underneath
161 # (if any). 220 # (if any).
162 self._collectors.pop() 221 self._collectors.pop()
163 if self._collectors: 222 if self._collectors:
164 self._collectors[-1].resume() 223 self._collectors[-1].resume()
165 224
166 def pause(self): 225 def pause(self):
167 """Pause tracing, but be prepared to `resume`.""" 226 """Pause tracing, but be prepared to `resume`."""
168 for tracer in self.tracers: 227 for tracer in self.tracers:
169 tracer.stop() 228 tracer.stop()
229 stats = tracer.get_stats()
230 if stats:
231 print("\nCoverage.py tracer stats:")
232 for k in sorted(stats.keys()):
233 print("%16s: %s" % (k, stats[k]))
170 threading.settrace(None) 234 threading.settrace(None)
171 235
172 def resume(self): 236 def resume(self):
173 """Resume tracing after a `pause`.""" 237 """Resume tracing after a `pause`."""
174 for tracer in self.tracers: 238 for tracer in self.tracers:
175 tracer.start() 239 tracer.start()
176 threading.settrace(self._installation_trace) 240 threading.settrace(self._installation_trace)
177 241
178 def data_points(self): 242 def get_line_data(self):
179 """Return the (filename, lineno) pairs collected.""" 243 """Return the line data collected.
180 return list(self.data.keys()) 244
245 Data is { filename: { lineno: None, ...}, ...}
246
247 """
248 if self.branch:
249 # If we were measuring branches, then we have to re-build the dict
250 # to show line data.
251 line_data = {}
252 for f, arcs in self.data.items():
253 line_data[f] = ldf = {}
254 for l1, _ in arcs:
255 if l1:
256 ldf[l1] = None
257 return line_data
258 else:
259 return self.data
260
261 def get_arc_data(self):
262 """Return the arc data collected.
263
264 Data is { filename: { (l1, l2): None, ...}, ...}
265
266 Note that no data is collected or returned if the Collector wasn't
267 created with `branch` true.
268
269 """
270 if self.branch:
271 return self.data
272 else:
273 return {}

eric ide

mercurial