Skip to content

Commit 0ed7fed

Browse files
committed
Add statistics gathering to help find a problem in the C tracer.
1 parent 2eab922 commit 0ed7fed

File tree

2 files changed

+94
-7
lines changed

2 files changed

+94
-7
lines changed

coverage/collector.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,10 @@ def stop(self):
9494
"""Stop this Tracer."""
9595
sys.settrace(None)
9696

97+
def get_stats(self):
98+
"""Return a dictionary of statistics, or None."""
99+
return None
100+
97101

98102
class Collector(object):
99103
"""Collects trace data.
@@ -203,9 +207,9 @@ def stop(self):
203207
assert self._collectors
204208
assert self._collectors[-1] is self
205209

206-
self.pause()
210+
self.pause()
207211
self.tracers = []
208-
212+
209213
# Remove this Collector from the stack, and resume the one underneath
210214
# (if any).
211215
self._collectors.pop()
@@ -216,6 +220,11 @@ def pause(self):
216220
"""Pause tracing, but be prepared to `resume`."""
217221
for tracer in self.tracers:
218222
tracer.stop()
223+
stats = tracer.get_stats()
224+
if stats:
225+
print("\nCoverage.py tracer stats:")
226+
for k in sorted(stats.keys()):
227+
print("%16s: %s" % (k, stats[k]))
219228
threading.settrace(None)
220229

221230
def resume(self):

coverage/tracer.c

Lines changed: 83 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,16 @@
66
#include "structmember.h"
77
#include "frameobject.h"
88

9-
#undef WHAT_LOG /* Define to log the WHAT params in the trace function. */
10-
#undef TRACE_LOG /* Define to log our bookkeeping. */
9+
/* Compile-time debugging helpers */
10+
#undef WHAT_LOG /* Define to log the WHAT params in the trace function. */
11+
#undef TRACE_LOG /* Define to log our bookkeeping. */
12+
#undef COLLECT_STATS /* Collect counters: stats are printed when tracer is stopped. */
13+
14+
#if COLLECT_STATS
15+
#define STATS(x) x
16+
#else
17+
#define STATS(x)
18+
#endif
1119

1220
/* Py 2.x and 3.x compatibility */
1321

@@ -85,14 +93,39 @@ typedef struct {
8593

8694
/* The parent frame for the last exception event, to fix missing returns. */
8795
PyFrameObject * last_exc_back;
88-
96+
97+
#if COLLECT_STATS
98+
struct {
99+
unsigned int calls;
100+
unsigned int lines;
101+
unsigned int returns;
102+
unsigned int exceptions;
103+
unsigned int others;
104+
unsigned int new_files;
105+
unsigned int missed_returns;
106+
unsigned int stack_reallocs;
107+
unsigned int errors;
108+
} stats;
109+
#endif /* COLLECT_STATS */
89110
} Tracer;
90111

91112
#define STACK_DELTA 100
92113

93114
static int
94115
Tracer_init(Tracer *self, PyObject *args, PyObject *kwds)
95116
{
117+
#if COLLECT_STATS
118+
self->stats.calls = 0;
119+
self->stats.lines = 0;
120+
self->stats.returns = 0;
121+
self->stats.exceptions = 0;
122+
self->stats.others = 0;
123+
self->stats.new_files = 0;
124+
self->stats.missed_returns = 0;
125+
self->stats.stack_reallocs = 0;
126+
self->stats.errors = 0;
127+
#endif /* COLLECT_STATS */
128+
96129
self->should_trace = NULL;
97130
self->data = NULL;
98131
self->should_trace_cache = NULL;
@@ -104,6 +137,7 @@ Tracer_init(Tracer *self, PyObject *args, PyObject *kwds)
104137
self->depth = -1;
105138
self->data_stack = PyMem_Malloc(STACK_DELTA*sizeof(DataStackEntry));
106139
if (self->data_stack == NULL) {
140+
STATS( self->stats.errors++; )
107141
return -1;
108142
}
109143
self->data_stack_alloc = STACK_DELTA;
@@ -191,11 +225,13 @@ Tracer_record_pair(Tracer *self, int l1, int l2)
191225
PyTuple_SET_ITEM(t, 0, MyInt_FromLong(l1));
192226
PyTuple_SET_ITEM(t, 1, MyInt_FromLong(l2));
193227
if (PyDict_SetItem(self->cur_file_data, t, Py_None) < 0) {
228+
STATS( self->stats.errors++; )
194229
ret = -1;
195230
}
196231
Py_DECREF(t);
197232
}
198233
else {
234+
STATS( self->stats.errors++; )
199235
ret = -1;
200236
}
201237
return ret;
@@ -235,6 +271,7 @@ Tracer_trace(Tracer *self, PyFrameObject *frame, int what, PyObject *arg)
235271
If someday we need to examine the frame when doing RETURN, then
236272
we'll need to keep more of the missed frame's state.
237273
*/
274+
STATS( self->stats.missed_returns++; )
238275
if (self->depth >= 0) {
239276
if (self->tracing_arcs && self->cur_file_data) {
240277
if (Tracer_record_pair(self, self->last_line, -1) < 0) {
@@ -253,13 +290,16 @@ Tracer_trace(Tracer *self, PyFrameObject *frame, int what, PyObject *arg)
253290

254291
switch (what) {
255292
case PyTrace_CALL: /* 0 */
293+
STATS( self->stats.calls++; )
256294
/* Grow the stack. */
257295
self->depth++;
258296
if (self->depth >= self->data_stack_alloc) {
297+
STATS( self->stats.stack_reallocs++; )
259298
/* We've outgrown our data_stack array: make it bigger. */
260299
int bigger = self->data_stack_alloc + STACK_DELTA;
261300
DataStackEntry * bigger_data_stack = PyMem_Realloc(self->data_stack, bigger * sizeof(DataStackEntry));
262301
if (bigger_data_stack == NULL) {
302+
STATS( self->stats.errors++; )
263303
self->depth--;
264304
return -1;
265305
}
@@ -275,13 +315,15 @@ Tracer_trace(Tracer *self, PyFrameObject *frame, int what, PyObject *arg)
275315
filename = frame->f_code->co_filename;
276316
tracename = PyDict_GetItem(self->should_trace_cache, filename);
277317
if (tracename == NULL) {
318+
STATS( self->stats.new_files++; )
278319
/* We've never considered this file before. */
279320
/* Ask should_trace about it. */
280321
PyObject * args = Py_BuildValue("(OO)", filename, frame);
281322
tracename = PyObject_Call(self->should_trace, args, NULL);
282323
Py_DECREF(args);
283324
if (tracename == NULL) {
284325
/* An error occurred inside should_trace. */
326+
STATS( self->stats.errors++; )
285327
return -1;
286328
}
287329
PyDict_SetItem(self->should_trace_cache, filename, tracename);
@@ -295,6 +337,10 @@ Tracer_trace(Tracer *self, PyFrameObject *frame, int what, PyObject *arg)
295337
PyObject * file_data = PyDict_GetItem(self->data, tracename);
296338
if (file_data == NULL) {
297339
file_data = PyDict_New();
340+
if (file_data == NULL) {
341+
STATS( self->stats.errors++; )
342+
return -1;
343+
}
298344
PyDict_SetItem(self->data, tracename, file_data);
299345
Py_DECREF(file_data);
300346
}
@@ -312,6 +358,7 @@ Tracer_trace(Tracer *self, PyFrameObject *frame, int what, PyObject *arg)
312358
break;
313359

314360
case PyTrace_RETURN: /* 3 */
361+
STATS( self->stats.returns++; )
315362
/* A near-copy of this code is above in the missing-return handler. */
316363
if (self->depth >= 0) {
317364
if (self->tracing_arcs && self->cur_file_data) {
@@ -328,6 +375,7 @@ Tracer_trace(Tracer *self, PyFrameObject *frame, int what, PyObject *arg)
328375
break;
329376

330377
case PyTrace_LINE: /* 2 */
378+
STATS( self->stats.lines++; )
331379
if (self->depth >= 0) {
332380
SHOWLOG(self->depth, frame->f_lineno, frame->f_code->co_filename, "line");
333381
if (self->cur_file_data) {
@@ -362,8 +410,13 @@ Tracer_trace(Tracer *self, PyFrameObject *frame, int what, PyObject *arg)
362410
363411
More about this fix: http://nedbatchelder.com/blog/200907/a_nasty_little_bug.html
364412
*/
413+
STATS( self->stats.exceptions++; )
365414
self->last_exc_back = frame->f_back;
366415
break;
416+
417+
default:
418+
STATS( self->stats.others++; )
419+
break;
367420
}
368421

369422
return 0;
@@ -391,6 +444,28 @@ Tracer_stop(Tracer *self, PyObject *args)
391444
return Py_BuildValue("");
392445
}
393446

447+
static PyObject *
448+
Tracer_get_stats(Tracer *self)
449+
{
450+
#if COLLECT_STATS
451+
return Py_BuildValue(
452+
"{sI,sI,sI,sI,sI,sI,sI,sI,si,sI}",
453+
"calls", self->stats.calls,
454+
"lines", self->stats.lines,
455+
"returns", self->stats.returns,
456+
"exceptions", self->stats.exceptions,
457+
"others", self->stats.others,
458+
"new_files", self->stats.new_files,
459+
"missed_returns", self->stats.missed_returns,
460+
"stack_reallocs", self->stats.stack_reallocs,
461+
"stack_alloc", self->data_stack_alloc,
462+
"errors", self->stats.errors
463+
);
464+
#else
465+
return Py_BuildValue("");
466+
#endif /* COLLECT_STATS */
467+
}
468+
394469
static PyMemberDef
395470
Tracer_members[] = {
396471
{ "should_trace", T_OBJECT, offsetof(Tracer, should_trace), 0,
@@ -410,12 +485,15 @@ Tracer_members[] = {
410485

411486
static PyMethodDef
412487
Tracer_methods[] = {
413-
{ "start", (PyCFunction) Tracer_start, METH_VARARGS,
488+
{ "start", (PyCFunction) Tracer_start, METH_VARARGS,
414489
PyDoc_STR("Start the tracer") },
415490

416-
{ "stop", (PyCFunction) Tracer_stop, METH_VARARGS,
491+
{ "stop", (PyCFunction) Tracer_stop, METH_VARARGS,
417492
PyDoc_STR("Stop the tracer") },
418493

494+
{ "get_stats", (PyCFunction) Tracer_get_stats, METH_VARARGS,
495+
PyDoc_STR("Get statistics about the tracing") },
496+
419497
{ NULL }
420498
};
421499

0 commit comments

Comments
 (0)