6
6
#include "structmember.h"
7
7
#include "frameobject.h"
8
8
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
11
19
12
20
/* Py 2.x and 3.x compatibility */
13
21
@@ -85,14 +93,39 @@ typedef struct {
85
93
86
94
/* The parent frame for the last exception event, to fix missing returns. */
87
95
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 */
89
110
} Tracer ;
90
111
91
112
#define STACK_DELTA 100
92
113
93
114
static int
94
115
Tracer_init (Tracer * self , PyObject * args , PyObject * kwds )
95
116
{
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
+
96
129
self -> should_trace = NULL ;
97
130
self -> data = NULL ;
98
131
self -> should_trace_cache = NULL ;
@@ -104,6 +137,7 @@ Tracer_init(Tracer *self, PyObject *args, PyObject *kwds)
104
137
self -> depth = -1 ;
105
138
self -> data_stack = PyMem_Malloc (STACK_DELTA * sizeof (DataStackEntry ));
106
139
if (self -> data_stack == NULL ) {
140
+ STATS ( self -> stats .errors ++ ; )
107
141
return -1 ;
108
142
}
109
143
self -> data_stack_alloc = STACK_DELTA ;
@@ -191,11 +225,13 @@ Tracer_record_pair(Tracer *self, int l1, int l2)
191
225
PyTuple_SET_ITEM (t , 0 , MyInt_FromLong (l1 ));
192
226
PyTuple_SET_ITEM (t , 1 , MyInt_FromLong (l2 ));
193
227
if (PyDict_SetItem (self -> cur_file_data , t , Py_None ) < 0 ) {
228
+ STATS ( self -> stats .errors ++ ; )
194
229
ret = -1 ;
195
230
}
196
231
Py_DECREF (t );
197
232
}
198
233
else {
234
+ STATS ( self -> stats .errors ++ ; )
199
235
ret = -1 ;
200
236
}
201
237
return ret ;
@@ -235,6 +271,7 @@ Tracer_trace(Tracer *self, PyFrameObject *frame, int what, PyObject *arg)
235
271
If someday we need to examine the frame when doing RETURN, then
236
272
we'll need to keep more of the missed frame's state.
237
273
*/
274
+ STATS ( self -> stats .missed_returns ++ ; )
238
275
if (self -> depth >= 0 ) {
239
276
if (self -> tracing_arcs && self -> cur_file_data ) {
240
277
if (Tracer_record_pair (self , self -> last_line , -1 ) < 0 ) {
@@ -253,13 +290,16 @@ Tracer_trace(Tracer *self, PyFrameObject *frame, int what, PyObject *arg)
253
290
254
291
switch (what ) {
255
292
case PyTrace_CALL : /* 0 */
293
+ STATS ( self -> stats .calls ++ ; )
256
294
/* Grow the stack. */
257
295
self -> depth ++ ;
258
296
if (self -> depth >= self -> data_stack_alloc ) {
297
+ STATS ( self -> stats .stack_reallocs ++ ; )
259
298
/* We've outgrown our data_stack array: make it bigger. */
260
299
int bigger = self -> data_stack_alloc + STACK_DELTA ;
261
300
DataStackEntry * bigger_data_stack = PyMem_Realloc (self -> data_stack , bigger * sizeof (DataStackEntry ));
262
301
if (bigger_data_stack == NULL ) {
302
+ STATS ( self -> stats .errors ++ ; )
263
303
self -> depth -- ;
264
304
return -1 ;
265
305
}
@@ -275,13 +315,15 @@ Tracer_trace(Tracer *self, PyFrameObject *frame, int what, PyObject *arg)
275
315
filename = frame -> f_code -> co_filename ;
276
316
tracename = PyDict_GetItem (self -> should_trace_cache , filename );
277
317
if (tracename == NULL ) {
318
+ STATS ( self -> stats .new_files ++ ; )
278
319
/* We've never considered this file before. */
279
320
/* Ask should_trace about it. */
280
321
PyObject * args = Py_BuildValue ("(OO)" , filename , frame );
281
322
tracename = PyObject_Call (self -> should_trace , args , NULL );
282
323
Py_DECREF (args );
283
324
if (tracename == NULL ) {
284
325
/* An error occurred inside should_trace. */
326
+ STATS ( self -> stats .errors ++ ; )
285
327
return -1 ;
286
328
}
287
329
PyDict_SetItem (self -> should_trace_cache , filename , tracename );
@@ -295,6 +337,10 @@ Tracer_trace(Tracer *self, PyFrameObject *frame, int what, PyObject *arg)
295
337
PyObject * file_data = PyDict_GetItem (self -> data , tracename );
296
338
if (file_data == NULL ) {
297
339
file_data = PyDict_New ();
340
+ if (file_data == NULL ) {
341
+ STATS ( self -> stats .errors ++ ; )
342
+ return -1 ;
343
+ }
298
344
PyDict_SetItem (self -> data , tracename , file_data );
299
345
Py_DECREF (file_data );
300
346
}
@@ -312,6 +358,7 @@ Tracer_trace(Tracer *self, PyFrameObject *frame, int what, PyObject *arg)
312
358
break ;
313
359
314
360
case PyTrace_RETURN : /* 3 */
361
+ STATS ( self -> stats .returns ++ ; )
315
362
/* A near-copy of this code is above in the missing-return handler. */
316
363
if (self -> depth >= 0 ) {
317
364
if (self -> tracing_arcs && self -> cur_file_data ) {
@@ -328,6 +375,7 @@ Tracer_trace(Tracer *self, PyFrameObject *frame, int what, PyObject *arg)
328
375
break ;
329
376
330
377
case PyTrace_LINE : /* 2 */
378
+ STATS ( self -> stats .lines ++ ; )
331
379
if (self -> depth >= 0 ) {
332
380
SHOWLOG (self -> depth , frame -> f_lineno , frame -> f_code -> co_filename , "line" );
333
381
if (self -> cur_file_data ) {
@@ -362,8 +410,13 @@ Tracer_trace(Tracer *self, PyFrameObject *frame, int what, PyObject *arg)
362
410
363
411
More about this fix: http://nedbatchelder.com/blog/200907/a_nasty_little_bug.html
364
412
*/
413
+ STATS ( self -> stats .exceptions ++ ; )
365
414
self -> last_exc_back = frame -> f_back ;
366
415
break ;
416
+
417
+ default :
418
+ STATS ( self -> stats .others ++ ; )
419
+ break ;
367
420
}
368
421
369
422
return 0 ;
@@ -391,6 +444,28 @@ Tracer_stop(Tracer *self, PyObject *args)
391
444
return Py_BuildValue ("" );
392
445
}
393
446
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
+
394
469
static PyMemberDef
395
470
Tracer_members [] = {
396
471
{ "should_trace" , T_OBJECT , offsetof(Tracer , should_trace ), 0 ,
@@ -410,12 +485,15 @@ Tracer_members[] = {
410
485
411
486
static PyMethodDef
412
487
Tracer_methods [] = {
413
- { "start" , (PyCFunction ) Tracer_start , METH_VARARGS ,
488
+ { "start" , (PyCFunction ) Tracer_start , METH_VARARGS ,
414
489
PyDoc_STR ("Start the tracer" ) },
415
490
416
- { "stop" , (PyCFunction ) Tracer_stop , METH_VARARGS ,
491
+ { "stop" , (PyCFunction ) Tracer_stop , METH_VARARGS ,
417
492
PyDoc_STR ("Stop the tracer" ) },
418
493
494
+ { "get_stats" , (PyCFunction ) Tracer_get_stats , METH_VARARGS ,
495
+ PyDoc_STR ("Get statistics about the tracing" ) },
496
+
419
497
{ NULL }
420
498
};
421
499
0 commit comments