Skip to content

Commit c69ebe8

Browse files
committed
Enable the profiling of C functions (builtins and extensions)
1 parent a1dde13 commit c69ebe8

File tree

10 files changed

+145
-15
lines changed

10 files changed

+145
-15
lines changed

Doc/api/init.tex

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -726,16 +726,21 @@ \section{Profiling and Tracing \label{profiling}}
726726
The first parameter is the object passed to the registration
727727
function as \var{obj}, \var{frame} is the frame object to which the
728728
event pertains, \var{what} is one of the constants
729-
\constant{PyTrace_CALL}, \constant{PyTrace_EXCEPT},
730-
\constant{PyTrace_LINE} or \constant{PyTrace_RETURN}, and \var{arg}
729+
\constant{PyTrace_CALL}, \constant{PyTrace_EXCEPTION},
730+
\constant{PyTrace_LINE}, \constant{PyTrace_RETURN},
731+
\constant{PyTrace_C_CALL}, \constant{PyTrace_C_EXCEPTION},
732+
or \constant{PyTrace_C_RETURN}, and \var{arg}
731733
depends on the value of \var{what}:
732734

733735
\begin{tableii}{l|l}{constant}{Value of \var{what}}{Meaning of \var{arg}}
734736
\lineii{PyTrace_CALL}{Always \NULL.}
735-
\lineii{PyTrace_EXCEPT}{Exception information as returned by
737+
\lineii{PyTrace_EXCEPTION}{Exception information as returned by
736738
\function{sys.exc_info()}.}
737739
\lineii{PyTrace_LINE}{Always \NULL.}
738740
\lineii{PyTrace_RETURN}{Value being returned to the caller.}
741+
\lineii{PyTrace_C_CALL}{Name of function being called.}
742+
\lineii{PyTrace_C_EXCEPTION}{Always \NULL.}
743+
\lineii{PyTrace_C_RETURN}{Always \NULL.}
739744
\end{tableii}
740745
\end{ctypedesc}
741746

@@ -747,7 +752,7 @@ \section{Profiling and Tracing \label{profiling}}
747752
control transfer to the Python bytecode in the corresponding frame.
748753
\end{cvardesc}
749754

750-
\begin{cvardesc}{int}{PyTrace_EXCEPT}
755+
\begin{cvardesc}{int}{PyTrace_EXCEPTION}
751756
The value of the \var{what} parameter to a \ctype{Py_tracefunc}
752757
function when an exception has been raised. The callback function
753758
is called with this value for \var{what} when after any bytecode is
@@ -770,6 +775,21 @@ \section{Profiling and Tracing \label{profiling}}
770775
functions when a call is returning without propagating an exception.
771776
\end{cvardesc}
772777

778+
\begin{cvardesc}{int}{PyTrace_C_CALL}
779+
The value for the \var{what} parameter to \ctype{Py_tracefunc}
780+
functions when a C function is about to be called.
781+
\end{cvardesc}
782+
783+
\begin{cvardesc}{int}{PyTrace_C_EXCEPTION}
784+
The value for the \var{what} parameter to \ctype{Py_tracefunc}
785+
functions when a C function has thrown an exception.
786+
\end{cvardesc}
787+
788+
\begin{cvardesc}{int}{PyTrace_C_RETURN}
789+
The value for the \var{what} parameter to \ctype{Py_tracefunc}
790+
functions when a C function has returned.
791+
\end{cvardesc}
792+
773793
\begin{cfuncdesc}{void}{PyEval_SetProfile}{Py_tracefunc func, PyObject *obj}
774794
Set the profiler function to \var{func}. The \var{obj} parameter is
775795
passed to the function as its first parameter, and may be any Python

Doc/lib/libpdb.tex

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -350,8 +350,9 @@ \section{How It Works \label{debugger-hooks}}
350350

351351
Trace functions have three arguments: \var{frame}, \var{event}, and
352352
\var{arg}. \var{frame} is the current stack frame. \var{event} is a
353-
string: \code{'call'}, \code{'line'}, \code{'return'} or
354-
\code{'exception'}. \var{arg} depends on the event type.
353+
string: \code{'call'}, \code{'line'}, \code{'return'}, \code{'exception'},
354+
\code{'c_call'}, \code{'c_return'}, or \code{'c_exception'}. \var{arg}
355+
depends on the event type.
355356

356357
The global trace function is invoked (with \var{event} set to
357358
\code{'call'}) whenever a new local scope is entered; it should return
@@ -390,6 +391,16 @@ \section{How It Works \label{debugger-hooks}}
390391
\var{traceback})}; the return value specifies the new local trace
391392
function.
392393

394+
\item[\code{'c_call'}]
395+
A C function is about to be called. This may be an extension function
396+
or a builtin. \var{arg} is the C function name.
397+
398+
\item[\code{'c_return'}]
399+
A C function has returned. \var{arg} is \code{None}.
400+
401+
\item[\code{'c_exception'}]
402+
A C function has thrown an exception. \var{arg} is \code{None}.
403+
393404
\end{description}
394405

395406
Note that as an exception is propagated down the chain of callers, an

Include/pystate.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,9 @@ typedef int (*Py_tracefunc)(PyObject *, struct _frame *, int, PyObject *);
4545
#define PyTrace_EXCEPTION 1
4646
#define PyTrace_LINE 2
4747
#define PyTrace_RETURN 3
48+
#define PyTrace_C_CALL 4
49+
#define PyTrace_C_EXCEPTION 5
50+
#define PyTrace_C_RETURN 6
4851

4952
typedef struct _ts {
5053

Lib/bdb.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,12 @@ def trace_dispatch(self, frame, event, arg):
5252
return self.dispatch_return(frame, arg)
5353
if event == 'exception':
5454
return self.dispatch_exception(frame, arg)
55+
if event == 'c_call':
56+
return self.trace_dispatch
57+
if event == 'c_exception':
58+
return self.trace_dispatch
59+
if event == 'c_return':
60+
return self.trace_dispatch
5561
print 'bdb.Bdb.dispatch: unknown debugging event:', repr(event)
5662
return self.trace_dispatch
5763

Lib/profile.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,7 @@ def __init__(self, timer=None, bias=None):
163163
self.timings = {}
164164
self.cur = None
165165
self.cmd = ""
166+
self.c_func_name = ""
166167

167168
if bias is None:
168169
bias = self.bias
@@ -214,6 +215,9 @@ def trace_dispatch(self, frame, event, arg):
214215
t = timer()
215216
t = t[0] + t[1] - self.t - self.bias
216217

218+
if event == "c_call":
219+
self.c_func_name = arg
220+
217221
if self.dispatch[event](self, frame,t):
218222
t = timer()
219223
self.t = t[0] + t[1]
@@ -227,7 +231,11 @@ def trace_dispatch(self, frame, event, arg):
227231
def trace_dispatch_i(self, frame, event, arg):
228232
timer = self.timer
229233
t = timer() - self.t - self.bias
230-
if self.dispatch[event](self, frame,t):
234+
235+
if event == "c_call":
236+
self.c_func_name = arg
237+
238+
if self.dispatch[event](self, frame, t):
231239
self.t = timer()
232240
else:
233241
self.t = timer() - t # put back unrecorded delta
@@ -238,6 +246,10 @@ def trace_dispatch_i(self, frame, event, arg):
238246
def trace_dispatch_mac(self, frame, event, arg):
239247
timer = self.timer
240248
t = timer()/60.0 - self.t - self.bias
249+
250+
if event == "c_call":
251+
self.c_func_name = arg
252+
241253
if self.dispatch[event](self, frame, t):
242254
self.t = timer()/60.0
243255
else:
@@ -249,6 +261,9 @@ def trace_dispatch_l(self, frame, event, arg):
249261
get_time = self.get_time
250262
t = get_time() - self.t - self.bias
251263

264+
if event == "c_call":
265+
self.c_func_name = arg
266+
252267
if self.dispatch[event](self, frame, t):
253268
self.t = get_time()
254269
else:
@@ -291,6 +306,17 @@ def trace_dispatch_call(self, frame, t):
291306
timings[fn] = 0, 0, 0, 0, {}
292307
return 1
293308

309+
def trace_dispatch_c_call (self, frame, t):
310+
fn = ("", 0, self.c_func_name)
311+
self.cur = (t, 0, 0, fn, frame, self.cur)
312+
timings = self.timings
313+
if timings.has_key(fn):
314+
cc, ns, tt, ct, callers = timings[fn]
315+
timings[fn] = cc, ns+1, tt, ct, callers
316+
else:
317+
timings[fn] = 0, 0, 0, 0, {}
318+
return 1
319+
294320
def trace_dispatch_return(self, frame, t):
295321
if frame is not self.cur[-2]:
296322
assert frame is self.cur[-2].f_back, ("Bad return", self.cur[-3])
@@ -333,6 +359,9 @@ def trace_dispatch_return(self, frame, t):
333359
"call": trace_dispatch_call,
334360
"exception": trace_dispatch_exception,
335361
"return": trace_dispatch_return,
362+
"c_call": trace_dispatch_c_call,
363+
"c_exception": trace_dispatch_exception,
364+
"c_return": trace_dispatch_return,
336365
}
337366

338367

Lib/test/output/test_profile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
test_profile
2-
53 function calls in 1.000 CPU seconds
2+
74 function calls in 1.000 CPU seconds
33

44
Ordered by: standard name
55

66
ncalls tottime percall cumtime percall filename:lineno(function)
7+
12 0.000 0.000 0.012 0.001 :0(hasattr)
8+
8 0.000 0.000 0.000 0.000 :0(range)
9+
1 0.000 0.000 0.000 0.000 :0(setprofile)
710
1 0.000 0.000 1.000 1.000 <string>:1(?)
811
0 0.000 0.000 profile:0(profiler)
912
1 0.000 0.000 1.000 1.000 profile:0(testfunc())

Lib/test/test_profilehooks.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,10 @@ def __init__(self):
1111
self.events = []
1212

1313
def callback(self, frame, event, arg):
14-
self.add_event(event, frame)
14+
if (event == "call"
15+
or event == "return"
16+
or event == "exception"):
17+
self.add_event(event, frame)
1518

1619
def add_event(self, event, frame=None):
1720
"""Add an event to the log."""
@@ -56,10 +59,16 @@ def trace_exception(self, frame):
5659
self.testcase.fail(
5760
"the profiler should never receive exception events")
5861

62+
def trace_pass(self, frame):
63+
pass
64+
5965
dispatch = {
6066
'call': trace_call,
6167
'exception': trace_exception,
6268
'return': trace_return,
69+
'c_call': trace_pass,
70+
'c_return': trace_pass,
71+
'c_exception': trace_pass,
6372
}
6473

6574

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ What's New in Python 2.4 alpha 1?
1212
Core and builtins
1313
-----------------
1414

15+
- Enabled the profiling of C extension functions (and builtins) - check
16+
new documentation and modified profiler and bdb modules for more details
17+
1518
- Set file.name to the object passed to open (instead of a new string)
1619

1720
- Moved tracebackobject into traceback.h and renamed to PyTracebackObject

Python/ceval.c

Lines changed: 48 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -536,9 +536,7 @@ _Py_CheckRecursiveCall(char *where)
536536
return 0;
537537
}
538538

539-
540539
/* Status code for main loop (reason for stack unwind) */
541-
542540
enum why_code {
543541
WHY_NOT, /* No error */
544542
WHY_EXCEPTION, /* Exception occurred */
@@ -3425,6 +3423,42 @@ err_args(PyObject *func, int flags, int nargs)
34253423
nargs);
34263424
}
34273425

3426+
#define BEGIN_C_TRACE \
3427+
if (tstate->use_tracing) { \
3428+
if (tstate->c_profilefunc != NULL) { \
3429+
PyObject *func_name = \
3430+
PyString_FromString (((PyCFunctionObject *) \
3431+
func)->m_ml->ml_name); \
3432+
are_tracing = 1; \
3433+
if (call_trace(tstate->c_profilefunc, \
3434+
tstate->c_profileobj, \
3435+
tstate->frame, PyTrace_C_CALL, \
3436+
func_name)) \
3437+
{ return NULL; } \
3438+
Py_DECREF (func_name); \
3439+
} \
3440+
}
3441+
3442+
#define END_C_TRACE \
3443+
if (tstate->use_tracing && are_tracing) { \
3444+
if (tstate->c_profilefunc != NULL) { \
3445+
if (x == NULL) { \
3446+
if (call_trace (tstate->c_profilefunc, \
3447+
tstate->c_profileobj, \
3448+
tstate->frame, PyTrace_C_EXCEPTION, \
3449+
NULL)) \
3450+
{ return NULL; } \
3451+
} else { \
3452+
if (call_trace(tstate->c_profilefunc, \
3453+
tstate->c_profileobj, \
3454+
tstate->frame, PyTrace_C_RETURN, \
3455+
NULL)) \
3456+
{ return NULL; } \
3457+
} \
3458+
} \
3459+
}
3460+
3461+
34283462
static PyObject *
34293463
call_function(PyObject ***pp_stack, int oparg)
34303464
{
@@ -3435,6 +3469,10 @@ call_function(PyObject ***pp_stack, int oparg)
34353469
PyObject *func = *pfunc;
34363470
PyObject *x, *w;
34373471

3472+
int are_tracing = 0;
3473+
3474+
PyThreadState *tstate = PyThreadState_GET();
3475+
34383476
/* Always dispatch PyCFunction first, because these are
34393477
presumed to be the most frequent callable object.
34403478
*/
@@ -3444,11 +3482,16 @@ call_function(PyObject ***pp_stack, int oparg)
34443482
if (flags & (METH_NOARGS | METH_O)) {
34453483
PyCFunction meth = PyCFunction_GET_FUNCTION(func);
34463484
PyObject *self = PyCFunction_GET_SELF(func);
3447-
if (flags & METH_NOARGS && na == 0)
3485+
if (flags & METH_NOARGS && na == 0) {
3486+
BEGIN_C_TRACE
34483487
x = (*meth)(self, NULL);
3488+
END_C_TRACE
3489+
}
34493490
else if (flags & METH_O && na == 1) {
34503491
PyObject *arg = EXT_POP(*pp_stack);
3492+
BEGIN_C_TRACE
34513493
x = (*meth)(self, arg);
3494+
END_C_TRACE
34523495
Py_DECREF(arg);
34533496
}
34543497
else {
@@ -3459,7 +3502,9 @@ call_function(PyObject ***pp_stack, int oparg)
34593502
else {
34603503
PyObject *callargs;
34613504
callargs = load_args(pp_stack, na);
3505+
BEGIN_C_TRACE
34623506
x = PyCFunction_Call(func, callargs, NULL);
3507+
END_C_TRACE
34633508
Py_XDECREF(callargs);
34643509
}
34653510
} else {

Python/sysmodule.c

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -272,15 +272,16 @@ operating system filenames."
272272
* Cached interned string objects used for calling the profile and
273273
* trace functions. Initialized by trace_init().
274274
*/
275-
static PyObject *whatstrings[4] = {NULL, NULL, NULL, NULL};
275+
static PyObject *whatstrings[7] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL};
276276

277277
static int
278278
trace_init(void)
279279
{
280-
static char *whatnames[4] = {"call", "exception", "line", "return"};
280+
static char *whatnames[7] = {"call", "exception", "line", "return",
281+
"c_call", "c_exception", "c_return"};
281282
PyObject *name;
282283
int i;
283-
for (i = 0; i < 4; ++i) {
284+
for (i = 0; i < 7; ++i) {
284285
if (whatstrings[i] == NULL) {
285286
name = PyString_InternFromString(whatnames[i]);
286287
if (name == NULL)

0 commit comments

Comments
 (0)