Revise the interface to the profiling and tracing support for the

Python interpreter.

This change adds two new C-level APIs:  PyEval_SetProfile() and
PyEval_SetTrace().  These can be used to install profile and trace
functions implemented in C, which can operate at much higher speeds
than Python-based functions.  The overhead for calling a C-based
profile function is a very small fraction of a percent of the overhead
involved in calling a Python-based function.

The machinery required to call a Python-based profile or trace
function been moved to sysmodule.c, where sys.setprofile() and
sys.setprofile() simply become users of the new interface.

As a side effect, SF bug #436058 is fixed; there is no longer a
_PyTrace_Init() function to declare.
This commit is contained in:
Fred Drake 2001-06-27 19:19:46 +00:00
parent 55fb6e0371
commit 5755ce693d
3 changed files with 182 additions and 150 deletions

View file

@ -2,7 +2,7 @@
/* Execute compiled code */ /* Execute compiled code */
/* XXX TO DO: /* XXX TO DO:
XXX how to pass arguments to call_trace? XXX how to pass arguments to profile and trace functions?
XXX speed up searching for keywords by using a dictionary XXX speed up searching for keywords by using a dictionary
XXX document it! XXX document it!
*/ */
@ -61,9 +61,9 @@ static PyObject *load_args(PyObject ***, int);
#ifdef LLTRACE #ifdef LLTRACE
static int prtrace(PyObject *, char *); static int prtrace(PyObject *, char *);
#endif #endif
static void call_exc_trace(PyObject **, PyObject**, PyFrameObject *); static int call_trace(Py_tracefunc, PyObject *, PyFrameObject *,
static int call_trace(PyObject **, PyObject **, int, PyObject *);
PyFrameObject *, PyObject *, PyObject *); static void call_exc_trace(Py_tracefunc, PyObject *, PyFrameObject *);
static PyObject *loop_subscript(PyObject *, PyObject *); static PyObject *loop_subscript(PyObject *, PyObject *);
static PyObject *apply_slice(PyObject *, PyObject *, PyObject *); static PyObject *apply_slice(PyObject *, PyObject *, PyObject *);
static int assign_slice(PyObject *, PyObject *, static int assign_slice(PyObject *, PyObject *,
@ -98,14 +98,6 @@ static long dxp[256];
#endif #endif
#endif #endif
/* Cached interned string objects used for calling the profile and
* trace functions.
*/
static PyObject *str_call = NULL;
static PyObject *str_exception = NULL;
static PyObject *str_line = NULL;
static PyObject *str_return = NULL;
staticforward PyTypeObject gentype; staticforward PyTypeObject gentype;
@ -1892,12 +1884,15 @@ eval_frame(PyFrameObject *f)
printf("--- %s:%d \n", filename, oparg); printf("--- %s:%d \n", filename, oparg);
#endif #endif
f->f_lineno = oparg; f->f_lineno = oparg;
if (f->f_trace == NULL) if (tstate->c_tracefunc == NULL || tstate->tracing)
continue; continue;
/* Trace each line of code reached */ /* Trace each line of code reached */
f->f_lasti = INSTR_OFFSET(); f->f_lasti = INSTR_OFFSET();
err = call_trace(&f->f_trace, &f->f_trace, /* Inline call_trace() for performance: */
f, str_line, Py_None); tstate->tracing++;
err = (tstate->c_tracefunc)(tstate->c_traceobj, f,
PyTrace_LINE, Py_None);
tstate->tracing--;
break; break;
case CALL_FUNCTION: case CALL_FUNCTION:
@ -2147,11 +2142,12 @@ eval_frame(PyFrameObject *f)
f->f_lasti -= 2; f->f_lasti -= 2;
PyTraceBack_Here(f); PyTraceBack_Here(f);
if (f->f_trace) if (tstate->c_tracefunc)
call_exc_trace(&f->f_trace, &f->f_trace, f); call_exc_trace(tstate->c_tracefunc,
if (tstate->sys_profilefunc) tstate->c_traceobj, f);
call_exc_trace(&tstate->sys_profilefunc, if (tstate->c_profilefunc)
(PyObject**)0, f); call_exc_trace(tstate->c_profilefunc,
tstate->c_profileobj, f);
} }
/* For the rest, treat WHY_RERAISE as WHY_EXCEPTION */ /* For the rest, treat WHY_RERAISE as WHY_EXCEPTION */
@ -2232,10 +2228,10 @@ eval_frame(PyFrameObject *f)
if (why != WHY_RETURN && why != WHY_YIELD) if (why != WHY_RETURN && why != WHY_YIELD)
retval = NULL; retval = NULL;
if (f->f_trace) { if (tstate->c_tracefunc && !tstate->tracing) {
if (why == WHY_RETURN || why == WHY_YIELD) { if (why == WHY_RETURN || why == WHY_YIELD) {
if (call_trace(&f->f_trace, &f->f_trace, f, if (call_trace(tstate->c_tracefunc, tstate->c_traceobj,
str_return, retval)) { f, PyTrace_RETURN, retval)) {
Py_XDECREF(retval); Py_XDECREF(retval);
retval = NULL; retval = NULL;
why = WHY_EXCEPTION; why = WHY_EXCEPTION;
@ -2243,10 +2239,10 @@ eval_frame(PyFrameObject *f)
} }
} }
if (tstate->sys_profilefunc && if (tstate->c_profilefunc && !tstate->tracing
(why == WHY_RETURN || why == WHY_YIELD)) { && (why == WHY_RETURN || why == WHY_YIELD)) {
if (call_trace(&tstate->sys_profilefunc, (PyObject**)0, if (call_trace(tstate->c_profilefunc, tstate->c_profileobj,
f, str_return, retval)) { f, PyTrace_RETURN, retval)) {
Py_XDECREF(retval); Py_XDECREF(retval);
retval = NULL; retval = NULL;
why = WHY_EXCEPTION; why = WHY_EXCEPTION;
@ -2475,7 +2471,7 @@ eval_code2(PyCodeObject *co, PyObject *globals, PyObject *locals,
} }
} }
if (tstate->sys_tracefunc != NULL) { if (tstate->c_tracefunc != NULL && !tstate->tracing) {
/* tstate->sys_tracefunc, if defined, is a function that /* tstate->sys_tracefunc, if defined, is a function that
will be called on *every* entry to a code block. will be called on *every* entry to a code block.
Its return value, if not None, is a function that Its return value, if not None, is a function that
@ -2488,20 +2484,21 @@ eval_code2(PyCodeObject *co, PyObject *globals, PyObject *locals,
depends on the situation. The global trace function depends on the situation. The global trace function
(sys.trace) is also called whenever an exception (sys.trace) is also called whenever an exception
is detected. */ is detected. */
if (call_trace(&tstate->sys_tracefunc, if (call_trace(tstate->c_tracefunc, tstate->c_traceobj,
&f->f_trace, f, str_call, f, PyTrace_CALL, Py_None)) {
Py_None/*XXX how to compute arguments now?*/)) { /* XXX Need way to compute arguments?? */
/* Trace function raised an error */ /* Trace function raised an error */
goto fail; goto fail;
} }
} }
if (tstate->sys_profilefunc != NULL) { if (tstate->c_profilefunc != NULL) {
/* Similar for sys_profilefunc, except it needn't return /* Similar for sys_profilefunc, except it needn't return
itself and isn't called for "line" events */ itself and isn't called for "line" events */
if (call_trace(&tstate->sys_profilefunc, if (call_trace(tstate->c_profilefunc, tstate->c_profileobj,
(PyObject**)0, f, str_call, f, PyTrace_CALL, Py_None)) {
Py_None/*XXX*/)) { /* XXX Need way to compute arguments?? */
/* Profile function raised an error */
goto fail; goto fail;
} }
} }
@ -2772,7 +2769,7 @@ prtrace(PyObject *v, char *str)
#endif #endif
static void static void
call_exc_trace(PyObject **p_trace, PyObject **p_newtrace, PyFrameObject *f) call_exc_trace(Py_tracefunc func, PyObject *self, PyFrameObject *f)
{ {
PyObject *type, *value, *traceback, *arg; PyObject *type, *value, *traceback, *arg;
int err; int err;
@ -2786,7 +2783,7 @@ call_exc_trace(PyObject **p_trace, PyObject **p_newtrace, PyFrameObject *f)
PyErr_Restore(type, value, traceback); PyErr_Restore(type, value, traceback);
return; return;
} }
err = call_trace(p_trace, p_newtrace, f, str_exception, arg); err = call_trace(func, self, f, PyTrace_EXCEPTION, arg);
Py_DECREF(arg); Py_DECREF(arg);
if (err == 0) if (err == 0)
PyErr_Restore(type, value, traceback); PyErr_Restore(type, value, traceback);
@ -2797,110 +2794,44 @@ call_exc_trace(PyObject **p_trace, PyObject **p_newtrace, PyFrameObject *f)
} }
} }
/* PyObject **p_trace: in/out; may not be NULL;
may not point to NULL variable initially
PyObject **p_newtrace: in/out; may be NULL;
may point to NULL variable;
may be same variable as p_newtrace
PyObject *msg: in; must not be NULL
*/
static int static int
call_trace(PyObject **p_trace, PyObject **p_newtrace, PyFrameObject *f, call_trace(Py_tracefunc func, PyObject *obj, PyFrameObject *frame,
PyObject *msg, PyObject *arg) int what, PyObject *arg)
{ {
PyThreadState *tstate = f->f_tstate; register PyThreadState *tstate = frame->f_tstate;
PyObject *args; int result;
PyObject *res = NULL; if (tstate->tracing)
if (tstate->tracing) {
/* Don't do recursive traces */
if (p_newtrace) {
Py_XDECREF(*p_newtrace);
*p_newtrace = NULL;
}
return 0; return 0;
}
args = PyTuple_New(3);
if (args == NULL)
goto cleanup;
Py_INCREF(msg);
Py_INCREF(f);
PyTuple_SET_ITEM(args, 0, (PyObject *)f);
PyTuple_SET_ITEM(args, 1, msg);
if (arg == NULL)
arg = Py_None;
Py_INCREF(arg);
PyTuple_SET_ITEM(args, 2, arg);
tstate->tracing++; tstate->tracing++;
PyFrame_FastToLocals(f); result = func(obj, frame, what, arg);
res = PyEval_CallObject(*p_trace, args); /* May clear *p_trace! */
PyFrame_LocalsToFast(f, 1);
tstate->tracing--; tstate->tracing--;
cleanup: return result;
Py_XDECREF(args);
if (res == NULL) {
/* The trace proc raised an exception */
PyTraceBack_Here(f);
Py_XDECREF(*p_trace);
*p_trace = NULL;
if (p_newtrace) {
Py_XDECREF(*p_newtrace);
*p_newtrace = NULL;
}
/* to be extra double plus sure we don't get recursive
* calls inf either tracefunc or profilefunc gets an
* exception, zap the global variables.
*/
Py_XDECREF(tstate->sys_tracefunc);
tstate->sys_tracefunc = NULL;
Py_XDECREF(tstate->sys_profilefunc);
tstate->sys_profilefunc = NULL;
return -1;
}
else {
if (p_newtrace) {
Py_XDECREF(*p_newtrace);
if (res == Py_None)
*p_newtrace = NULL;
else {
Py_INCREF(res);
*p_newtrace = res;
}
}
Py_DECREF(res);
return 0;
}
} }
/* Initialize the strings that get passed to the profile and trace functions; void
* this avoids doing this while we're actually profiling/tracing. PyEval_SetProfile(Py_tracefunc func, PyObject *arg)
*/
int
_PyTrace_Init(void)
{ {
if (str_call == NULL) { PyThreadState *tstate = PyThreadState_Get();
str_call = PyString_InternFromString("call"); PyObject *temp = tstate->c_profileobj;
if (str_call == NULL) Py_XINCREF(arg);
return -1; tstate->c_profilefunc = NULL;
} tstate->c_profileobj = NULL;
if (str_exception == NULL) { Py_XDECREF(temp);
str_exception = PyString_InternFromString("exception"); tstate->c_profilefunc = func;
if (str_exception == NULL) tstate->c_profileobj = arg;
return -1; }
}
if (str_line == NULL) { void
str_line = PyString_InternFromString("line"); PyEval_SetTrace(Py_tracefunc func, PyObject *arg)
if (str_line == NULL) {
return -1; PyThreadState *tstate = PyThreadState_Get();
} PyObject *temp = tstate->c_traceobj;
if (str_return == NULL) { Py_XINCREF(arg);
str_return = PyString_InternFromString("return"); tstate->c_tracefunc = NULL;
if (str_return == NULL) tstate->c_traceobj = NULL;
return -1; Py_XDECREF(temp);
} tstate->c_tracefunc = func;
return 0; tstate->c_traceobj = arg;
} }
PyObject * PyObject *

View file

@ -120,8 +120,10 @@ PyThreadState_New(PyInterpreterState *interp)
tstate->exc_value = NULL; tstate->exc_value = NULL;
tstate->exc_traceback = NULL; tstate->exc_traceback = NULL;
tstate->sys_profilefunc = NULL; tstate->c_profilefunc = NULL;
tstate->sys_tracefunc = NULL; tstate->c_tracefunc = NULL;
tstate->c_profileobj = NULL;
tstate->c_traceobj = NULL;
HEAD_LOCK(); HEAD_LOCK();
tstate->next = interp->tstate_head; tstate->next = interp->tstate_head;
@ -152,8 +154,10 @@ PyThreadState_Clear(PyThreadState *tstate)
ZAP(tstate->exc_value); ZAP(tstate->exc_value);
ZAP(tstate->exc_traceback); ZAP(tstate->exc_traceback);
ZAP(tstate->sys_profilefunc); tstate->c_profilefunc = NULL;
ZAP(tstate->sys_tracefunc); tstate->c_tracefunc = NULL;
ZAP(tstate->c_profileobj);
ZAP(tstate->c_traceobj);
} }

View file

@ -196,20 +196,120 @@ static char setdefaultencoding_doc[] =
\n\ \n\
Set the current default string encoding used by the Unicode implementation."; Set the current default string encoding used by the Unicode implementation.";
extern int _PyTrace_Init(void); /*
* Cached interned string objects used for calling the profile and
* trace functions. Initialized by trace_init().
*/
static PyObject *whatstrings[4] = {NULL, NULL, NULL, NULL};
static int
trace_init(void)
{
static char *whatnames[4] = {"call", "exception", "line", "return"};
PyObject *name;
int i;
for (i = 0; i < 4; ++i) {
if (whatstrings[i] == NULL) {
name = PyString_InternFromString(whatnames[i]);
if (name == NULL)
return -1;
whatstrings[i] = name;
}
}
return 0;
}
static PyObject *
call_trampoline(PyThreadState *tstate, PyObject* callback,
PyFrameObject *frame, int what, PyObject *arg)
{
PyObject *args = PyTuple_New(3);
PyObject *whatstr;
PyObject *result;
if (args == NULL)
return NULL;
Py_INCREF(frame);
whatstr = whatstrings[what];
Py_INCREF(whatstr);
if (arg == NULL)
arg = Py_None;
Py_INCREF(arg);
PyTuple_SET_ITEM(args, 0, (PyObject *)frame);
PyTuple_SET_ITEM(args, 1, whatstr);
PyTuple_SET_ITEM(args, 2, arg);
/* call the Python-level function */
PyFrame_FastToLocals(frame);
result = PyEval_CallObject(callback, args);
PyFrame_LocalsToFast(frame, 1);
if (result == NULL)
PyTraceBack_Here(frame);
/* cleanup */
Py_DECREF(args);
return result;
}
static int
profile_trampoline(PyObject *self, PyFrameObject *frame,
int what, PyObject *arg)
{
PyThreadState *tstate = frame->f_tstate;
PyObject *result;
result = call_trampoline(tstate, self, frame, what, arg);
if (result == NULL) {
PyEval_SetProfile(NULL, NULL);
return -1;
}
Py_DECREF(result);
return 0;
}
static int
trace_trampoline(PyObject *self, PyFrameObject *frame,
int what, PyObject *arg)
{
PyThreadState *tstate = frame->f_tstate;
PyObject *callback;
PyObject *result;
if (what == PyTrace_CALL)
callback = self;
else
callback = frame->f_trace;
if (callback == NULL)
return 0;
result = call_trampoline(tstate, callback, frame, what, arg);
if (result == NULL) {
PyEval_SetTrace(NULL, NULL);
Py_XDECREF(frame->f_trace);
frame->f_trace = NULL;
return -1;
}
if (result != Py_None) {
PyObject *temp = frame->f_trace;
frame->f_trace = NULL;
Py_XDECREF(temp);
frame->f_trace = result;
}
else {
Py_DECREF(result);
}
return 0;
}
static PyObject * static PyObject *
sys_settrace(PyObject *self, PyObject *args) sys_settrace(PyObject *self, PyObject *args)
{ {
PyThreadState *tstate = PyThreadState_Get(); if (trace_init() == -1)
if (_PyTrace_Init() == -1)
return NULL; return NULL;
if (args == Py_None) if (args == Py_None)
args = NULL; PyEval_SetTrace(NULL, NULL);
else else
Py_XINCREF(args); PyEval_SetTrace(trace_trampoline, args);
Py_XDECREF(tstate->sys_tracefunc);
tstate->sys_tracefunc = args;
Py_INCREF(Py_None); Py_INCREF(Py_None);
return Py_None; return Py_None;
} }
@ -223,15 +323,12 @@ function call. See the debugger chapter in the library manual.";
static PyObject * static PyObject *
sys_setprofile(PyObject *self, PyObject *args) sys_setprofile(PyObject *self, PyObject *args)
{ {
PyThreadState *tstate = PyThreadState_Get(); if (trace_init() == -1)
if (_PyTrace_Init() == -1)
return NULL; return NULL;
if (args == Py_None) if (args == Py_None)
args = NULL; PyEval_SetProfile(NULL, NULL);
else else
Py_XINCREF(args); PyEval_SetProfile(profile_trampoline, args);
Py_XDECREF(tstate->sys_profilefunc);
tstate->sys_profilefunc = args;
Py_INCREF(Py_None); Py_INCREF(Py_None);
return Py_None; return Py_None;
} }