diff options
Diffstat (limited to 'coverage/tracer.c')
-rw-r--r-- | coverage/tracer.c | 185 |
1 files changed, 145 insertions, 40 deletions
diff --git a/coverage/tracer.c b/coverage/tracer.c index f05988d3..1d227295 100644 --- a/coverage/tracer.c +++ b/coverage/tracer.c @@ -6,8 +6,16 @@ #include "structmember.h"
#include "frameobject.h"
-#undef WHAT_LOG /* Define to log the WHAT params in the trace function. */
-#undef TRACE_LOG /* Define to log our bookkeeping. */
+/* Compile-time debugging helpers */
+#undef WHAT_LOG /* Define to log the WHAT params in the trace function. */
+#undef TRACE_LOG /* Define to log our bookkeeping. */
+#undef COLLECT_STATS /* Collect counters: stats are printed when tracer is stopped. */
+
+#if COLLECT_STATS
+#define STATS(x) x
+#else
+#define STATS(x)
+#endif
/* Py 2.x and 3.x compatibility */
@@ -33,6 +41,10 @@ #endif /* Py3k */
+/* The values returned to indicate ok or error. */
+#define RET_OK 0
+#define RET_ERROR -1
+
/* An entry on the data stack. For each call frame, we need to record the
dictionary to capture data, and the last line number executed in that
frame.
@@ -52,7 +64,7 @@ typedef struct { PyObject * data;
PyObject * should_trace_cache;
PyObject * arcs;
-
+
/* Has the tracer been started? */
int started;
/* Are we tracing arcs, or just lines? */
@@ -63,7 +75,7 @@ typedef struct { data for a single source file. The data stack parallels the call stack:
each call pushes the new frame's file data onto the data stack, and each
return pops file data off.
-
+
The file data is a dictionary whose form depends on the tracing options.
If tracing arcs, the keys are line number pairs. If not tracing arcs,
the keys are line numbers. In both cases, the value is irrelevant
@@ -85,7 +97,21 @@ typedef struct { /* The parent frame for the last exception event, to fix missing returns. */
PyFrameObject * last_exc_back;
-
+ int last_exc_firstlineno;
+
+#if COLLECT_STATS
+ struct {
+ unsigned int calls;
+ unsigned int lines;
+ unsigned int returns;
+ unsigned int exceptions;
+ unsigned int others;
+ unsigned int new_files;
+ unsigned int missed_returns;
+ unsigned int stack_reallocs;
+ unsigned int errors;
+ } stats;
+#endif /* COLLECT_STATS */
} Tracer;
#define STACK_DELTA 100
@@ -93,27 +119,41 @@ typedef struct { static int
Tracer_init(Tracer *self, PyObject *args, PyObject *kwds)
{
+#if COLLECT_STATS
+ self->stats.calls = 0;
+ self->stats.lines = 0;
+ self->stats.returns = 0;
+ self->stats.exceptions = 0;
+ self->stats.others = 0;
+ self->stats.new_files = 0;
+ self->stats.missed_returns = 0;
+ self->stats.stack_reallocs = 0;
+ self->stats.errors = 0;
+#endif /* COLLECT_STATS */
+
self->should_trace = NULL;
self->data = NULL;
self->should_trace_cache = NULL;
self->arcs = NULL;
-
+
self->started = 0;
self->tracing_arcs = 0;
self->depth = -1;
self->data_stack = PyMem_Malloc(STACK_DELTA*sizeof(DataStackEntry));
if (self->data_stack == NULL) {
- return -1;
+ STATS( self->stats.errors++; )
+ PyErr_NoMemory();
+ return RET_ERROR;
}
self->data_stack_alloc = STACK_DELTA;
- self->cur_file_data = NULL;
+ self->cur_file_data = NULL;
self->last_line = -1;
self->last_exc_back = NULL;
- return 0;
+ return RET_OK;
}
static void
@@ -136,7 +176,7 @@ Tracer_dealloc(Tracer *self) static const char *
indent(int n)
{
- static const char * spaces =
+ static const char * spaces =
" "
" "
" "
@@ -184,19 +224,21 @@ static const char * what_sym[] = {"CALL", "EXC ", "LINE", "RET "}; static int
Tracer_record_pair(Tracer *self, int l1, int l2)
{
- int ret = 0;
-
+ int ret = RET_OK;
+
PyObject * t = PyTuple_New(2);
if (t != NULL) {
PyTuple_SET_ITEM(t, 0, MyInt_FromLong(l1));
PyTuple_SET_ITEM(t, 1, MyInt_FromLong(l2));
if (PyDict_SetItem(self->cur_file_data, t, Py_None) < 0) {
- ret = -1;
+ STATS( self->stats.errors++; )
+ ret = RET_ERROR;
}
Py_DECREF(t);
}
else {
- ret = -1;
+ STATS( self->stats.errors++; )
+ ret = RET_ERROR;
}
return ret;
}
@@ -207,14 +249,15 @@ Tracer_record_pair(Tracer *self, int l1, int l2) static int
Tracer_trace(Tracer *self, PyFrameObject *frame, int what, PyObject *arg)
{
+ int ret = RET_OK;
PyObject * filename = NULL;
PyObject * tracename = NULL;
- #if WHAT_LOG
+ #if WHAT_LOG
if (what <= sizeof(what_sym)/sizeof(const char *)) {
printf("trace: %s @ %s %d\n", what_sym[what], MyText_AS_STRING(frame->f_code->co_filename), frame->f_lineno);
}
- #endif
+ #endif
#if TRACE_LOG
if (strstr(MyText_AS_STRING(frame->f_code->co_filename), start_file) && frame->f_lineno == start_line) {
@@ -231,14 +274,15 @@ Tracer_trace(Tracer *self, PyFrameObject *frame, int what, PyObject *arg) that frame is gone. Our handling for RETURN doesn't need the
actual frame, but we do log it, so that will look a little off if
you're looking at the detailed log.
-
+
If someday we need to examine the frame when doing RETURN, then
we'll need to keep more of the missed frame's state.
*/
+ STATS( self->stats.missed_returns++; )
if (self->depth >= 0) {
if (self->tracing_arcs && self->cur_file_data) {
- if (Tracer_record_pair(self, self->last_line, -1) < 0) {
- return -1;
+ if (Tracer_record_pair(self, self->last_line, -self->last_exc_firstlineno) < 0) {
+ return RET_ERROR;
}
}
SHOWLOG(self->depth, frame->f_lineno, frame->f_code->co_filename, "missedreturn");
@@ -249,19 +293,23 @@ Tracer_trace(Tracer *self, PyFrameObject *frame, int what, PyObject *arg) }
self->last_exc_back = NULL;
}
-
+
switch (what) {
case PyTrace_CALL: /* 0 */
+ STATS( self->stats.calls++; )
/* Grow the stack. */
self->depth++;
if (self->depth >= self->data_stack_alloc) {
+ STATS( self->stats.stack_reallocs++; )
/* We've outgrown our data_stack array: make it bigger. */
int bigger = self->data_stack_alloc + STACK_DELTA;
DataStackEntry * bigger_data_stack = PyMem_Realloc(self->data_stack, bigger * sizeof(DataStackEntry));
if (bigger_data_stack == NULL) {
+ STATS( self->stats.errors++; )
+ PyErr_NoMemory();
self->depth--;
- return -1;
+ return RET_ERROR;
}
self->data_stack = bigger_data_stack;
self->data_stack_alloc = bigger;
@@ -275,6 +323,7 @@ Tracer_trace(Tracer *self, PyFrameObject *frame, int what, PyObject *arg) filename = frame->f_code->co_filename;
tracename = PyDict_GetItem(self->should_trace_cache, filename);
if (tracename == NULL) {
+ STATS( self->stats.new_files++; )
/* We've never considered this file before. */
/* Ask should_trace about it. */
PyObject * args = Py_BuildValue("(OO)", filename, frame);
@@ -282,9 +331,13 @@ Tracer_trace(Tracer *self, PyFrameObject *frame, int what, PyObject *arg) Py_DECREF(args);
if (tracename == NULL) {
/* An error occurred inside should_trace. */
- return -1;
+ STATS( self->stats.errors++; )
+ return RET_ERROR;
+ }
+ if (PyDict_SetItem(self->should_trace_cache, filename, tracename) < 0) {
+ STATS( self->stats.errors++; )
+ return RET_ERROR;
}
- PyDict_SetItem(self->should_trace_cache, filename, tracename);
}
else {
Py_INCREF(tracename);
@@ -295,8 +348,16 @@ Tracer_trace(Tracer *self, PyFrameObject *frame, int what, PyObject *arg) PyObject * file_data = PyDict_GetItem(self->data, tracename);
if (file_data == NULL) {
file_data = PyDict_New();
- PyDict_SetItem(self->data, tracename, file_data);
+ if (file_data == NULL) {
+ STATS( self->stats.errors++; )
+ return RET_ERROR;
+ }
+ ret = PyDict_SetItem(self->data, tracename, file_data);
Py_DECREF(file_data);
+ if (ret < 0) {
+ STATS( self->stats.errors++; )
+ return RET_ERROR;
+ }
}
self->cur_file_data = file_data;
SHOWLOG(self->depth, frame->f_lineno, filename, "traced");
@@ -305,18 +366,20 @@ Tracer_trace(Tracer *self, PyFrameObject *frame, int what, PyObject *arg) self->cur_file_data = NULL;
SHOWLOG(self->depth, frame->f_lineno, filename, "skipped");
}
-
+
Py_DECREF(tracename);
self->last_line = -1;
break;
-
+
case PyTrace_RETURN: /* 3 */
+ STATS( self->stats.returns++; )
/* A near-copy of this code is above in the missing-return handler. */
if (self->depth >= 0) {
if (self->tracing_arcs && self->cur_file_data) {
- if (Tracer_record_pair(self, self->last_line, -1) < 0) {
- return -1;
+ int first = frame->f_code->co_firstlineno;
+ if (Tracer_record_pair(self, self->last_line, -first) < 0) {
+ return RET_ERROR;
}
}
@@ -326,8 +389,9 @@ Tracer_trace(Tracer *self, PyFrameObject *frame, int what, PyObject *arg) self->depth--;
}
break;
-
+
case PyTrace_LINE: /* 2 */
+ STATS( self->stats.lines++; )
if (self->depth >= 0) {
SHOWLOG(self->depth, frame->f_lineno, frame->f_code->co_filename, "line");
if (self->cur_file_data) {
@@ -335,38 +399,54 @@ Tracer_trace(Tracer *self, PyFrameObject *frame, int what, PyObject *arg) if (self->tracing_arcs) {
/* Tracing arcs: key is (last_line,this_line). */
if (Tracer_record_pair(self, self->last_line, frame->f_lineno) < 0) {
- return -1;
+ return RET_ERROR;
}
}
else {
/* Tracing lines: key is simply this_line. */
- PyDict_SetItem(self->cur_file_data, MyInt_FromLong(frame->f_lineno), Py_None);
+ PyObject * this_line = MyInt_FromLong(frame->f_lineno);
+ if (this_line == NULL) {
+ STATS( self->stats.errors++; )
+ return RET_ERROR;
+ }
+ ret = PyDict_SetItem(self->cur_file_data, this_line, Py_None);
+ Py_DECREF(this_line);
+ if (ret < 0) {
+ STATS( self->stats.errors++; )
+ return RET_ERROR;
+ }
}
}
self->last_line = frame->f_lineno;
}
break;
-
+
case PyTrace_EXCEPTION:
/* Some code (Python 2.3, and pyexpat anywhere) fires an exception event
without a return event. To detect that, we'll keep a copy of the
parent frame for an exception event. If the next event is in that
frame, then we must have returned without a return event. We can
synthesize the missing event then.
-
+
Python itself fixed this problem in 2.4. Pyexpat still has the bug.
I've reported the problem with pyexpat as http://bugs.python.org/issue6359 .
If it gets fixed, this code should still work properly. Maybe some day
the bug will be fixed everywhere coverage.py is supported, and we can
remove this missing-return detection.
-
+
More about this fix: http://nedbatchelder.com/blog/200907/a_nasty_little_bug.html
*/
+ STATS( self->stats.exceptions++; )
self->last_exc_back = frame->f_back;
+ self->last_exc_firstlineno = frame->f_code->co_firstlineno;
+ break;
+
+ default:
+ STATS( self->stats.others++; )
break;
}
- return 0;
+ return RET_OK;
}
static PyObject *
@@ -391,6 +471,28 @@ Tracer_stop(Tracer *self, PyObject *args) return Py_BuildValue("");
}
+static PyObject *
+Tracer_get_stats(Tracer *self)
+{
+#if COLLECT_STATS
+ return Py_BuildValue(
+ "{sI,sI,sI,sI,sI,sI,sI,sI,si,sI}",
+ "calls", self->stats.calls,
+ "lines", self->stats.lines,
+ "returns", self->stats.returns,
+ "exceptions", self->stats.exceptions,
+ "others", self->stats.others,
+ "new_files", self->stats.new_files,
+ "missed_returns", self->stats.missed_returns,
+ "stack_reallocs", self->stats.stack_reallocs,
+ "stack_alloc", self->data_stack_alloc,
+ "errors", self->stats.errors
+ );
+#else
+ return Py_BuildValue("");
+#endif /* COLLECT_STATS */
+}
+
static PyMemberDef
Tracer_members[] = {
{ "should_trace", T_OBJECT, offsetof(Tracer, should_trace), 0,
@@ -410,12 +512,15 @@ Tracer_members[] = { static PyMethodDef
Tracer_methods[] = {
- { "start", (PyCFunction) Tracer_start, METH_VARARGS,
+ { "start", (PyCFunction) Tracer_start, METH_VARARGS,
PyDoc_STR("Start the tracer") },
- { "stop", (PyCFunction) Tracer_stop, METH_VARARGS,
+ { "stop", (PyCFunction) Tracer_stop, METH_VARARGS,
PyDoc_STR("Stop the tracer") },
+ { "get_stats", (PyCFunction) Tracer_get_stats, METH_VARARGS,
+ PyDoc_STR("Get statistics about the tracing") },
+
{ NULL }
};
@@ -488,7 +593,7 @@ PyInit_tracer(void) if (mod == NULL) {
return NULL;
}
-
+
TracerType.tp_new = PyType_GenericNew;
if (PyType_Ready(&TracerType) < 0) {
Py_DECREF(mod);
@@ -497,8 +602,8 @@ PyInit_tracer(void) Py_INCREF(&TracerType);
PyModule_AddObject(mod, "Tracer", (PyObject *)&TracerType);
-
- return mod;
+
+ return mod;
}
#else
|