38 # Attributes set from the collector: |
38 # Attributes set from the collector: |
39 self.data = None |
39 self.data = None |
40 self.trace_arcs = False |
40 self.trace_arcs = False |
41 self.should_trace = None |
41 self.should_trace = None |
42 self.should_trace_cache = None |
42 self.should_trace_cache = None |
|
43 self.should_start_context = None |
43 self.warn = None |
44 self.warn = None |
44 # The threading module to use, if any. |
45 # The threading module to use, if any. |
45 self.threading = None |
46 self.threading = None |
46 |
47 |
47 self.cur_file_dict = None |
48 self.cur_file_dict = None |
48 self.last_line = 0 # int, but uninitialized. |
49 self.last_line = 0 # int, but uninitialized. |
49 self.cur_file_name = None |
50 self.cur_file_name = None |
|
51 self.context = None |
|
52 self.started_context = False |
50 |
53 |
51 self.data_stack = [] |
54 self.data_stack = [] |
52 self.last_exc_back = None |
55 self.last_exc_back = None |
53 self.last_exc_firstlineno = 0 |
56 self.last_exc_firstlineno = 0 |
54 self.thread = None |
57 self.thread = None |
58 self.in_atexit = False |
61 self.in_atexit = False |
59 # On exit, self.in_atexit = True |
62 # On exit, self.in_atexit = True |
60 atexit.register(setattr, self, 'in_atexit', True) |
63 atexit.register(setattr, self, 'in_atexit', True) |
61 |
64 |
62 def __repr__(self): |
65 def __repr__(self): |
63 return "<PyTracer at {0}: {1} lines in {2} files>".format( |
66 return "<PyTracer at {}: {} lines in {} files>".format( |
64 id(self), |
67 id(self), |
65 sum(len(v) for v in self.data.values()), |
68 sum(len(v) for v in self.data.values()), |
66 len(self.data), |
69 len(self.data), |
67 ) |
70 ) |
68 |
71 |
81 def _trace(self, frame, event, arg_unused): |
84 def _trace(self, frame, event, arg_unused): |
82 """The trace function passed to sys.settrace.""" |
85 """The trace function passed to sys.settrace.""" |
83 |
86 |
84 #self.log(":", frame.f_code.co_filename, frame.f_lineno, event) |
87 #self.log(":", frame.f_code.co_filename, frame.f_lineno, event) |
85 |
88 |
86 if (self.stopped and sys.gettrace() == self._trace): |
89 if (self.stopped and sys.gettrace() == self._trace): # pylint: disable=comparison-with-callable |
87 # The PyTrace.stop() method has been called, possibly by another |
90 # The PyTrace.stop() method has been called, possibly by another |
88 # thread, let's deactivate ourselves now. |
91 # thread, let's deactivate ourselves now. |
89 #self.log("X", frame.f_code.co_filename, frame.f_lineno) |
92 #self.log("X", frame.f_code.co_filename, frame.f_lineno) |
90 sys.settrace(None) |
93 sys.settrace(None) |
91 return None |
94 return None |
94 if frame == self.last_exc_back: |
97 if frame == self.last_exc_back: |
95 # Someone forgot a return event. |
98 # Someone forgot a return event. |
96 if self.trace_arcs and self.cur_file_dict: |
99 if self.trace_arcs and self.cur_file_dict: |
97 pair = (self.last_line, -self.last_exc_firstlineno) |
100 pair = (self.last_line, -self.last_exc_firstlineno) |
98 self.cur_file_dict[pair] = None |
101 self.cur_file_dict[pair] = None |
99 self.cur_file_dict, self.cur_file_name, self.last_line = self.data_stack.pop() |
102 self.cur_file_dict, self.cur_file_name, self.last_line, self.started_context = ( |
|
103 self.data_stack.pop() |
|
104 ) |
100 self.last_exc_back = None |
105 self.last_exc_back = None |
101 |
106 |
102 if event == 'call': |
107 if event == 'call': |
103 # Entering a new function context. Decide if we should trace |
108 # Should we start a new context? |
|
109 if self.should_start_context and self.context is None: |
|
110 context_maybe = self.should_start_context(frame) |
|
111 if context_maybe is not None: |
|
112 self.context = context_maybe |
|
113 self.started_context = True |
|
114 self.switch_context(self.context) |
|
115 else: |
|
116 self.started_context = False |
|
117 else: |
|
118 self.started_context = False |
|
119 |
|
120 # Entering a new frame. Decide if we should trace |
104 # in this file. |
121 # in this file. |
105 self._activity = True |
122 self._activity = True |
106 self.data_stack.append((self.cur_file_dict, self.cur_file_name, self.last_line)) |
123 self.data_stack.append( |
|
124 ( |
|
125 self.cur_file_dict, |
|
126 self.cur_file_name, |
|
127 self.last_line, |
|
128 self.started_context, |
|
129 ) |
|
130 ) |
107 filename = frame.f_code.co_filename |
131 filename = frame.f_code.co_filename |
108 self.cur_file_name = filename |
132 self.cur_file_name = filename |
109 disp = self.should_trace_cache.get(filename) |
133 disp = self.should_trace_cache.get(filename) |
110 if disp is None: |
134 if disp is None: |
111 disp = self.should_trace(filename, frame) |
135 disp = self.should_trace(filename, frame) |
144 code = frame.f_code.co_code |
168 code = frame.f_code.co_code |
145 if (not code) or code[frame.f_lasti] != YIELD_VALUE: |
169 if (not code) or code[frame.f_lasti] != YIELD_VALUE: |
146 first = frame.f_code.co_firstlineno |
170 first = frame.f_code.co_firstlineno |
147 self.cur_file_dict[(self.last_line, -first)] = None |
171 self.cur_file_dict[(self.last_line, -first)] = None |
148 # Leaving this function, pop the filename stack. |
172 # Leaving this function, pop the filename stack. |
149 self.cur_file_dict, self.cur_file_name, self.last_line = self.data_stack.pop() |
173 self.cur_file_dict, self.cur_file_name, self.last_line, self.started_context = ( |
|
174 self.data_stack.pop() |
|
175 ) |
|
176 # Leaving a context? |
|
177 if self.started_context: |
|
178 self.context = None |
|
179 self.switch_context(None) |
150 elif event == 'exception': |
180 elif event == 'exception': |
151 self.last_exc_back = frame.f_back |
181 self.last_exc_back = frame.f_back |
152 self.last_exc_firstlineno = frame.f_code.co_firstlineno |
182 self.last_exc_firstlineno = frame.f_code.co_firstlineno |
153 return self._trace |
183 return self._trace |
154 |
184 |
173 sys.settrace(self._trace) |
203 sys.settrace(self._trace) |
174 return self._trace |
204 return self._trace |
175 |
205 |
176 def stop(self): |
206 def stop(self): |
177 """Stop this Tracer.""" |
207 """Stop this Tracer.""" |
178 # Get the activate tracer callback before setting the stop flag to be |
208 # Get the active tracer callback before setting the stop flag to be |
179 # able to detect if the tracer was changed prior to stopping it. |
209 # able to detect if the tracer was changed prior to stopping it. |
180 tf = sys.gettrace() |
210 tf = sys.gettrace() |
181 |
211 |
182 # Set the stop flag. The actual call to sys.settrace(None) will happen |
212 # Set the stop flag. The actual call to sys.settrace(None) will happen |
183 # in the self._trace callback itself to make sure to call it from the |
213 # in the self._trace callback itself to make sure to call it from the |
194 if self.warn: |
224 if self.warn: |
195 # PyPy clears the trace function before running atexit functions, |
225 # PyPy clears the trace function before running atexit functions, |
196 # so don't warn if we are in atexit on PyPy and the trace function |
226 # so don't warn if we are in atexit on PyPy and the trace function |
197 # has changed to None. |
227 # has changed to None. |
198 dont_warn = (env.PYPY and env.PYPYVERSION >= (5, 4) and self.in_atexit and tf is None) |
228 dont_warn = (env.PYPY and env.PYPYVERSION >= (5, 4) and self.in_atexit and tf is None) |
199 if (not dont_warn) and tf != self._trace: |
229 if (not dont_warn) and tf != self._trace: # pylint: disable=comparison-with-callable |
200 self.warn( |
230 self.warn( |
201 "Trace function changed, measurement is likely wrong: %r" % (tf,), |
231 "Trace function changed, measurement is likely wrong: %r" % (tf,), |
202 slug="trace-changed", |
232 slug="trace-changed", |
203 ) |
233 ) |
204 |
234 |