summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGES.txt4
-rw-r--r--coverage/collector.py14
-rw-r--r--coverage/control.py60
-rw-r--r--coverage/tracer.c165
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 */