| Index: tools/telemetry/third_party/coverage/coverage/pytracer.py
|
| diff --git a/tools/telemetry/third_party/coverage/coverage/pytracer.py b/tools/telemetry/third_party/coverage/coverage/pytracer.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..cdb3ae7077faa28768d653582fcf3e4a4048e6c6
|
| --- /dev/null
|
| +++ b/tools/telemetry/third_party/coverage/coverage/pytracer.py
|
| @@ -0,0 +1,152 @@
|
| +# Licensed under the Apache License: http://www.apache.org/licenses/LICENSE-2.0
|
| +# For details: https://bitbucket.org/ned/coveragepy/src/default/NOTICE.txt
|
| +
|
| +"""Raw data collector for coverage.py."""
|
| +
|
| +import dis
|
| +import sys
|
| +
|
| +from coverage import env
|
| +
|
| +# We need the YIELD_VALUE opcode below, in a comparison-friendly form.
|
| +YIELD_VALUE = dis.opmap['YIELD_VALUE']
|
| +if env.PY2:
|
| + YIELD_VALUE = chr(YIELD_VALUE)
|
| +
|
| +
|
| +class PyTracer(object):
|
| + """Python implementation of the raw data tracer."""
|
| +
|
| + # Because of poor implementations of trace-function-manipulating tools,
|
| + # the Python trace function must be kept very simple. In particular, there
|
| + # must be only one function ever set as the trace function, both through
|
| + # sys.settrace, and as the return value from the trace function. Put
|
| + # another way, the trace function must always return itself. It cannot
|
| + # swap in other functions, or return None to avoid tracing a particular
|
| + # frame.
|
| + #
|
| + # The trace manipulator that introduced this restriction is DecoratorTools,
|
| + # which sets a trace function, and then later restores the pre-existing one
|
| + # by calling sys.settrace with a function it found in the current frame.
|
| + #
|
| + # Systems that use DecoratorTools (or similar trace manipulations) must use
|
| + # PyTracer to get accurate results. The command-line --timid argument is
|
| + # used to force the use of this tracer.
|
| +
|
| + def __init__(self):
|
| + # Attributes set from the collector:
|
| + self.data = None
|
| + self.trace_arcs = False
|
| + self.should_trace = None
|
| + self.should_trace_cache = None
|
| + self.warn = None
|
| + # The threading module to use, if any.
|
| + self.threading = None
|
| +
|
| + self.cur_file_dict = []
|
| + self.last_line = [0]
|
| +
|
| + self.data_stack = []
|
| + self.last_exc_back = None
|
| + self.last_exc_firstlineno = 0
|
| + self.thread = None
|
| + self.stopped = False
|
| +
|
| + def __repr__(self):
|
| + return "<PyTracer at 0x{0:0x}: {1} lines in {2} files>".format(
|
| + id(self),
|
| + sum(len(v) for v in self.data.values()),
|
| + len(self.data),
|
| + )
|
| +
|
| + def _trace(self, frame, event, arg_unused):
|
| + """The trace function passed to sys.settrace."""
|
| +
|
| + if self.stopped:
|
| + return
|
| +
|
| + if self.last_exc_back:
|
| + if frame == self.last_exc_back:
|
| + # Someone forgot a return event.
|
| + if self.trace_arcs and self.cur_file_dict:
|
| + pair = (self.last_line, -self.last_exc_firstlineno)
|
| + self.cur_file_dict[pair] = None
|
| + self.cur_file_dict, self.last_line = self.data_stack.pop()
|
| + self.last_exc_back = None
|
| +
|
| + if event == 'call':
|
| + # Entering a new function context. Decide if we should trace
|
| + # in this file.
|
| + self.data_stack.append((self.cur_file_dict, self.last_line))
|
| + filename = frame.f_code.co_filename
|
| + disp = self.should_trace_cache.get(filename)
|
| + if disp is None:
|
| + disp = self.should_trace(filename, frame)
|
| + self.should_trace_cache[filename] = disp
|
| +
|
| + self.cur_file_dict = None
|
| + if disp.trace:
|
| + tracename = disp.source_filename
|
| + if tracename not in self.data:
|
| + self.data[tracename] = {}
|
| + self.cur_file_dict = self.data[tracename]
|
| + # The call event is really a "start frame" event, and happens for
|
| + # function calls and re-entering generators. The f_lasti field is
|
| + # -1 for calls, and a real offset for generators. Use -1 as the
|
| + # line number for calls, and the real line number for generators.
|
| + self.last_line = -1 if (frame.f_lasti < 0) else frame.f_lineno
|
| + elif event == 'line':
|
| + # Record an executed line.
|
| + if self.cur_file_dict is not None:
|
| + lineno = frame.f_lineno
|
| + if self.trace_arcs:
|
| + self.cur_file_dict[(self.last_line, lineno)] = None
|
| + else:
|
| + self.cur_file_dict[lineno] = None
|
| + self.last_line = lineno
|
| + elif event == 'return':
|
| + if self.trace_arcs and self.cur_file_dict:
|
| + # Record an arc leaving the function, but beware that a
|
| + # "return" event might just mean yielding from a generator.
|
| + bytecode = frame.f_code.co_code[frame.f_lasti]
|
| + if bytecode != YIELD_VALUE:
|
| + first = frame.f_code.co_firstlineno
|
| + self.cur_file_dict[(self.last_line, -first)] = None
|
| + # Leaving this function, pop the filename stack.
|
| + self.cur_file_dict, self.last_line = self.data_stack.pop()
|
| + elif event == 'exception':
|
| + self.last_exc_back = frame.f_back
|
| + self.last_exc_firstlineno = frame.f_code.co_firstlineno
|
| + return self._trace
|
| +
|
| + def start(self):
|
| + """Start this Tracer.
|
| +
|
| + Return a Python function suitable for use with sys.settrace().
|
| +
|
| + """
|
| + if self.threading:
|
| + self.thread = self.threading.currentThread()
|
| + sys.settrace(self._trace)
|
| + self.stopped = False
|
| + return self._trace
|
| +
|
| + def stop(self):
|
| + """Stop this Tracer."""
|
| + self.stopped = True
|
| + if self.threading and self.thread != self.threading.currentThread():
|
| + # Called on a different thread than started us: we can't unhook
|
| + # ourselves, but we've set the flag that we should stop, so we
|
| + # won't do any more tracing.
|
| + return
|
| +
|
| + if self.warn:
|
| + if sys.gettrace() != self._trace:
|
| + msg = "Trace function changed, measurement is likely wrong: %r"
|
| + self.warn(msg % (sys.gettrace(),))
|
| +
|
| + sys.settrace(None)
|
| +
|
| + def get_stats(self):
|
| + """Return a dictionary of statistics, or None."""
|
| + return None
|
|
|