diff options
-rw-r--r-- | CHANGES.txt | 4 | ||||
-rw-r--r-- | coverage/collector.py | 14 | ||||
-rw-r--r-- | coverage/control.py | 60 | ||||
-rw-r--r-- | coverage/tracer.c | 165 |
4 files changed, 183 insertions, 60 deletions
diff --git a/CHANGES.txt b/CHANGES.txt index a926e5f9..0883caa5 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -53,6 +53,9 @@ Latest - Combining data files with an explicit configuration file was broken in 4.0a6, but now works again, closing `issue 385`_. +- The speed is back to 3.7.1 levels, after having slowed down due to plugin + support, finishing up `issue 387`_. + .. _issue 236: https://bitbucket.org/ned/coveragepy/issues/236/pickles-are-bad-and-you-should-feel-bad .. _issue 262: https://bitbucket.org/ned/coveragepy/issues/262/when-parallel-true-erase-should-erase-all .. _issue 275: https://bitbucket.org/ned/coveragepy/issues/275/refer-consistently-to-project-as-coverage @@ -60,6 +63,7 @@ Latest .. _issue 380: https://bitbucket.org/ned/coveragepy/issues/380/code-executed-by-exec-excluded-from .. _issue 385: https://bitbucket.org/ned/coveragepy/issues/385/coverage-combine-doesnt-work-with-rcfile .. _issue 386: https://bitbucket.org/ned/coveragepy/issues/386/error-on-unrecognised-configuration +.. _issue 387: https://bitbucket.org/ned/coveragepy/issues/387/performance-degradation-from-371-to-40 .. 41 issues closed in 4.0 below here diff --git a/coverage/collector.py b/coverage/collector.py index b8e08414..ae5f6c8b 100644 --- a/coverage/collector.py +++ b/coverage/collector.py @@ -13,7 +13,7 @@ from coverage.pytracer import PyTracer try: # Use the C extension code when we can, for speed. - from coverage.tracer import CTracer # pylint: disable=no-name-in-module + from coverage.tracer import CTracer, CFileDisposition # pylint: disable=no-name-in-module except ImportError: # Couldn't import the C extension, maybe it isn't built. if os.getenv('COVERAGE_TEST_TRACER') == 'c': @@ -30,6 +30,11 @@ except ImportError: CTracer = None +class FileDisposition(object): + """A simple value type for recording what to do with a file.""" + pass + + class Collector(object): """Collects trace data. @@ -124,7 +129,12 @@ class Collector(object): # trace function. self._trace_class = CTracer or PyTracer - self.supports_plugins = self._trace_class is CTracer + if self._trace_class is CTracer: + self.file_disposition_class = CFileDisposition + self.supports_plugins = True + else: + self.file_disposition_class = FileDisposition + self.supports_plugins = False def __repr__(self): return "<Collector at 0x%x>" % id(self) diff --git a/coverage/control.py b/coverage/control.py index 3ff7e2a1..4396374e 100644 --- a/coverage/control.py +++ b/coverage/control.py @@ -427,7 +427,7 @@ class Coverage(object): """ original_filename = filename - disp = FileDisposition(filename) + disp = _disposition_init(self.collector.file_disposition_class, filename) def nope(disp, reason): """Simple helper to make it easy to return NO.""" @@ -570,7 +570,7 @@ class Coverage(object): """ disp = self._should_trace_internal(filename, frame) if self.debug.should('trace'): - self.debug.write(disp.debug_message()) + self.debug.write(_disposition_debug_msg(disp)) return disp def _check_include_omit_etc(self, filename, frame): @@ -1065,36 +1065,32 @@ class Coverage(object): return info -class FileDisposition(object): - """A simple object for noting a number of details of files to trace.""" - def __init__(self, original_filename): - self.original_filename = original_filename - self.canonical_filename = original_filename - self.source_filename = None - self.trace = False - self.reason = "" - self.file_tracer = None - self.has_dynamic_filename = False - - def __repr__(self): - ret = "FileDisposition %r" % (self.original_filename,) - if self.trace: - ret += " trace" - else: - ret += " notrace=%r" % (self.reason,) - if self.file_tracer: - ret += " file_tracer=%r" % (self.file_tracer,) - return "<" + ret + ">" - - def debug_message(self): - """Produce a debugging message explaining the outcome.""" - if self.trace: - msg = "Tracing %r" % (self.original_filename,) - if self.file_tracer: - msg += ": will be traced by %r" % self.file_tracer - else: - msg = "Not tracing %r: %s" % (self.original_filename, self.reason) - return msg +# FileDisposition "methods": FileDisposition is a pure value object, so it can +# be implemented in either C or Python. Acting on them is done with these +# functions. + +def _disposition_init(cls, original_filename): + """Construct and initialize a new FileDisposition object.""" + disp = cls() + disp.original_filename = original_filename + disp.canonical_filename = original_filename + disp.source_filename = None + disp.trace = False + disp.reason = "" + disp.file_tracer = None + disp.has_dynamic_filename = False + return disp + + +def _disposition_debug_msg(disp): + """Make a nice debug message of what the FileDisposition is doing.""" + if disp.trace: + msg = "Tracing %r" % (disp.original_filename,) + if disp.file_tracer: + msg += ": will be traced by %r" % disp.file_tracer + else: + msg = "Not tracing %r: %s" % (disp.original_filename, disp.reason) + return msg def process_startup(): diff --git a/coverage/tracer.c b/coverage/tracer.c index 25415d75..a56cc000 100644 --- a/coverage/tracer.c +++ b/coverage/tracer.c @@ -74,13 +74,15 @@ pyint_as_int(PyObject * pyint, int *pint) /* An entry on the data stack. For each call frame, we need to record all * the information needed for CTracer_handle_line to operate as quickly as - * possible. + * possible. All PyObject* here are borrowed references. */ typedef struct DataStackEntry { /* The current file_data dictionary. Borrowed, owned by self->data. */ PyObject * file_data; - /* The disposition object for this frame. */ + /* The disposition object for this frame. If collector.py and control.py + * are working properly, this will be an instance of CFileDisposition. + */ PyObject * disposition; /* The FileTracer handling this frame, or None if it's Python. */ @@ -101,15 +103,105 @@ typedef struct DataStack { } DataStack; +typedef struct CFileDisposition { + PyObject_HEAD + + PyObject * original_filename; + PyObject * canonical_filename; + PyObject * source_filename; + PyObject * trace; + PyObject * reason; + PyObject * file_tracer; + PyObject * has_dynamic_filename; +} CFileDisposition; + +static void +CFileDisposition_dealloc(CFileDisposition *self) +{ + Py_XDECREF(self->original_filename); + Py_XDECREF(self->canonical_filename); + Py_XDECREF(self->source_filename); + Py_XDECREF(self->trace); + Py_XDECREF(self->reason); + Py_XDECREF(self->file_tracer); + Py_XDECREF(self->has_dynamic_filename); +} + +static PyMemberDef +CFileDisposition_members[] = { + { "original_filename", T_OBJECT, offsetof(CFileDisposition, original_filename), 0, + PyDoc_STR("") }, + + { "canonical_filename", T_OBJECT, offsetof(CFileDisposition, canonical_filename), 0, + PyDoc_STR("") }, + + { "source_filename", T_OBJECT, offsetof(CFileDisposition, source_filename), 0, + PyDoc_STR("") }, + + { "trace", T_OBJECT, offsetof(CFileDisposition, trace), 0, + PyDoc_STR("") }, + + { "reason", T_OBJECT, offsetof(CFileDisposition, reason), 0, + PyDoc_STR("") }, + + { "file_tracer", T_OBJECT, offsetof(CFileDisposition, file_tracer), 0, + PyDoc_STR("") }, + + { "has_dynamic_filename", T_OBJECT, offsetof(CFileDisposition, has_dynamic_filename), 0, + PyDoc_STR("") }, + + { NULL } +}; + +static PyTypeObject +CFileDispositionType = { + MyType_HEAD_INIT + "coverage.CFileDispositionType", /*tp_name*/ + sizeof(CFileDisposition), /*tp_basicsize*/ + 0, /*tp_itemsize*/ + (destructor)CFileDisposition_dealloc, /*tp_dealloc*/ + 0, /*tp_print*/ + 0, /*tp_getattr*/ + 0, /*tp_setattr*/ + 0, /*tp_compare*/ + 0, /*tp_repr*/ + 0, /*tp_as_number*/ + 0, /*tp_as_sequence*/ + 0, /*tp_as_mapping*/ + 0, /*tp_hash */ + 0, /*tp_call*/ + 0, /*tp_str*/ + 0, /*tp_getattro*/ + 0, /*tp_setattro*/ + 0, /*tp_as_buffer*/ + Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/ + "CFileDisposition objects", /* tp_doc */ + 0, /* tp_traverse */ + 0, /* tp_clear */ + 0, /* tp_richcompare */ + 0, /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + CFileDisposition_members, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + 0, /* tp_new */ +}; + /* Interned strings to speed GetAttr etc. */ static PyObject *str_trace; -static PyObject *str_source_filename; static PyObject *str_file_tracer; static PyObject *str__coverage_enabled; static PyObject *str__coverage_plugin; static PyObject *str__coverage_plugin_name; -static PyObject *str_has_dynamic_filename; static PyObject *str_dynamic_source_filename; static PyObject *str_line_number_range; @@ -125,12 +217,10 @@ intern_strings() } INTERN_STRING(str_trace, "trace") - INTERN_STRING(str_source_filename, "source_filename") INTERN_STRING(str_file_tracer, "file_tracer") INTERN_STRING(str__coverage_enabled, "_coverage_enabled") INTERN_STRING(str__coverage_plugin, "_coverage_plugin") INTERN_STRING(str__coverage_plugin_name, "_coverage_plugin_name") - INTERN_STRING(str_has_dynamic_filename, "has_dynamic_filename") INTERN_STRING(str_dynamic_source_filename, "dynamic_source_filename") INTERN_STRING(str_line_number_range, "line_number_range") @@ -503,16 +593,19 @@ CTracer_handle_call(CTracer *self, PyFrameObject *frame) int ret2; /* Owned references that we clean up at the very end of the function. */ - PyObject * tracename = NULL; PyObject * disposition = NULL; - PyObject * disp_trace = NULL; - PyObject * file_tracer = NULL; PyObject * plugin = NULL; PyObject * plugin_name = NULL; - PyObject * has_dynamic_filename = NULL; + PyObject * next_tracename = NULL; /* Borrowed references. */ PyObject * filename = NULL; + PyObject * disp_trace = NULL; + PyObject * tracename = NULL; + PyObject * file_tracer = NULL; + PyObject * has_dynamic_filename = NULL; + + CFileDisposition * pdisp; STATS( self->stats.calls++; ) @@ -555,10 +648,11 @@ CTracer_handle_call(CTracer *self, PyFrameObject *frame) if (disposition == Py_None) { /* A later check_include returned false, so don't trace it. */ disp_trace = Py_False; - Py_INCREF(Py_False); } else { - disp_trace = PyObject_GetAttr(disposition, str_trace); + /* The object we got is a CFileDisposition, use it efficiently. */ + pdisp = (CFileDisposition *) disposition; + disp_trace = pdisp->trace; if (disp_trace == NULL) { goto error; } @@ -566,11 +660,11 @@ CTracer_handle_call(CTracer *self, PyFrameObject *frame) if (disp_trace == Py_True) { /* If tracename is a string, then we're supposed to trace. */ - tracename = PyObject_GetAttr(disposition, str_source_filename); + tracename = pdisp->source_filename; if (tracename == NULL) { goto error; } - file_tracer = PyObject_GetAttr(disposition, str_file_tracer); + file_tracer = pdisp->file_tracer; if (file_tracer == NULL) { goto error; } @@ -584,12 +678,11 @@ CTracer_handle_call(CTracer *self, PyFrameObject *frame) goto error; } } - has_dynamic_filename = PyObject_GetAttr(disposition, str_has_dynamic_filename); + has_dynamic_filename = pdisp->has_dynamic_filename; if (has_dynamic_filename == NULL) { goto error; } if (has_dynamic_filename == Py_True) { - PyObject * next_tracename = NULL; STATS( self->stats.pycalls++; ) next_tracename = PyObject_CallMethodObjArgs( file_tracer, str_dynamic_source_filename, @@ -603,7 +696,6 @@ CTracer_handle_call(CTracer *self, PyFrameObject *frame) /* Because we handled the error, goto ok. */ goto ok; } - Py_DECREF(tracename); tracename = next_tracename; if (tracename != Py_None) { @@ -632,16 +724,13 @@ CTracer_handle_call(CTracer *self, PyFrameObject *frame) should_include = (included != Py_None); } if (!should_include) { - Py_DECREF(tracename); tracename = Py_None; - Py_INCREF(tracename); } } } } else { tracename = Py_None; - Py_INCREF(tracename); } if (tracename != Py_None) { @@ -696,13 +785,10 @@ ok: ret = RET_OK; error: - Py_XDECREF(tracename); + Py_XDECREF(next_tracename); Py_XDECREF(disposition); - Py_XDECREF(disp_trace); - Py_XDECREF(file_tracer); Py_XDECREF(plugin); Py_XDECREF(plugin_name); - Py_XDECREF(has_dynamic_filename); return ret; } @@ -1254,6 +1340,11 @@ PyInit_tracer(void) return NULL; } + if (intern_strings() < 0) { + return NULL; + } + + /* Initialize CTracer */ CTracerType.tp_new = PyType_GenericNew; if (PyType_Ready(&CTracerType) < 0) { Py_DECREF(mod); @@ -1267,7 +1358,19 @@ PyInit_tracer(void) return NULL; } - if (intern_strings() < 0) { + /* Initialize CFileDisposition */ + CFileDispositionType.tp_new = PyType_GenericNew; + if (PyType_Ready(&CFileDispositionType) < 0) { + Py_DECREF(mod); + Py_DECREF(&CTracerType); + return NULL; + } + + Py_INCREF(&CFileDispositionType); + if (PyModule_AddObject(mod, "CFileDisposition", (PyObject *)&CFileDispositionType) < 0) { + Py_DECREF(mod); + Py_DECREF(&CTracerType); + Py_DECREF(&CFileDispositionType); return NULL; } @@ -1286,6 +1389,11 @@ inittracer(void) return; } + if (intern_strings() < 0) { + return; + } + + /* Initialize CTracer */ CTracerType.tp_new = PyType_GenericNew; if (PyType_Ready(&CTracerType) < 0) { return; @@ -1294,9 +1402,14 @@ inittracer(void) Py_INCREF(&CTracerType); PyModule_AddObject(mod, "CTracer", (PyObject *)&CTracerType); - if (intern_strings() < 0) { + /* Initialize CFileDisposition */ + CFileDispositionType.tp_new = PyType_GenericNew; + if (PyType_Ready(&CFileDispositionType) < 0) { return; } + + Py_INCREF(&CFileDispositionType); + PyModule_AddObject(mod, "CFileDisposition", (PyObject *)&CFileDispositionType); } #endif /* Py3k */ |