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:
parent
55fb6e0371
commit
5755ce693d
197
Python/ceval.c
197
Python/ceval.c
|
@ -2,7 +2,7 @@
|
|||
/* Execute compiled code */
|
||||
|
||||
/* 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 document it!
|
||||
*/
|
||||
|
@ -61,9 +61,9 @@ static PyObject *load_args(PyObject ***, int);
|
|||
#ifdef LLTRACE
|
||||
static int prtrace(PyObject *, char *);
|
||||
#endif
|
||||
static void call_exc_trace(PyObject **, PyObject**, PyFrameObject *);
|
||||
static int call_trace(PyObject **, PyObject **,
|
||||
PyFrameObject *, PyObject *, PyObject *);
|
||||
static int call_trace(Py_tracefunc, PyObject *, PyFrameObject *,
|
||||
int, PyObject *);
|
||||
static void call_exc_trace(Py_tracefunc, PyObject *, PyFrameObject *);
|
||||
static PyObject *loop_subscript(PyObject *, PyObject *);
|
||||
static PyObject *apply_slice(PyObject *, PyObject *, PyObject *);
|
||||
static int assign_slice(PyObject *, PyObject *,
|
||||
|
@ -98,14 +98,6 @@ static long dxp[256];
|
|||
#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;
|
||||
|
||||
|
@ -1892,12 +1884,15 @@ eval_frame(PyFrameObject *f)
|
|||
printf("--- %s:%d \n", filename, oparg);
|
||||
#endif
|
||||
f->f_lineno = oparg;
|
||||
if (f->f_trace == NULL)
|
||||
if (tstate->c_tracefunc == NULL || tstate->tracing)
|
||||
continue;
|
||||
/* Trace each line of code reached */
|
||||
f->f_lasti = INSTR_OFFSET();
|
||||
err = call_trace(&f->f_trace, &f->f_trace,
|
||||
f, str_line, Py_None);
|
||||
/* Inline call_trace() for performance: */
|
||||
tstate->tracing++;
|
||||
err = (tstate->c_tracefunc)(tstate->c_traceobj, f,
|
||||
PyTrace_LINE, Py_None);
|
||||
tstate->tracing--;
|
||||
break;
|
||||
|
||||
case CALL_FUNCTION:
|
||||
|
@ -2147,11 +2142,12 @@ eval_frame(PyFrameObject *f)
|
|||
f->f_lasti -= 2;
|
||||
PyTraceBack_Here(f);
|
||||
|
||||
if (f->f_trace)
|
||||
call_exc_trace(&f->f_trace, &f->f_trace, f);
|
||||
if (tstate->sys_profilefunc)
|
||||
call_exc_trace(&tstate->sys_profilefunc,
|
||||
(PyObject**)0, f);
|
||||
if (tstate->c_tracefunc)
|
||||
call_exc_trace(tstate->c_tracefunc,
|
||||
tstate->c_traceobj, f);
|
||||
if (tstate->c_profilefunc)
|
||||
call_exc_trace(tstate->c_profilefunc,
|
||||
tstate->c_profileobj, f);
|
||||
}
|
||||
|
||||
/* For the rest, treat WHY_RERAISE as WHY_EXCEPTION */
|
||||
|
@ -2232,10 +2228,10 @@ eval_frame(PyFrameObject *f)
|
|||
if (why != WHY_RETURN && why != WHY_YIELD)
|
||||
retval = NULL;
|
||||
|
||||
if (f->f_trace) {
|
||||
if (tstate->c_tracefunc && !tstate->tracing) {
|
||||
if (why == WHY_RETURN || why == WHY_YIELD) {
|
||||
if (call_trace(&f->f_trace, &f->f_trace, f,
|
||||
str_return, retval)) {
|
||||
if (call_trace(tstate->c_tracefunc, tstate->c_traceobj,
|
||||
f, PyTrace_RETURN, retval)) {
|
||||
Py_XDECREF(retval);
|
||||
retval = NULL;
|
||||
why = WHY_EXCEPTION;
|
||||
|
@ -2243,10 +2239,10 @@ eval_frame(PyFrameObject *f)
|
|||
}
|
||||
}
|
||||
|
||||
if (tstate->sys_profilefunc &&
|
||||
(why == WHY_RETURN || why == WHY_YIELD)) {
|
||||
if (call_trace(&tstate->sys_profilefunc, (PyObject**)0,
|
||||
f, str_return, retval)) {
|
||||
if (tstate->c_profilefunc && !tstate->tracing
|
||||
&& (why == WHY_RETURN || why == WHY_YIELD)) {
|
||||
if (call_trace(tstate->c_profilefunc, tstate->c_profileobj,
|
||||
f, PyTrace_RETURN, retval)) {
|
||||
Py_XDECREF(retval);
|
||||
retval = NULL;
|
||||
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
|
||||
will be called on *every* entry to a code block.
|
||||
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
|
||||
(sys.trace) is also called whenever an exception
|
||||
is detected. */
|
||||
if (call_trace(&tstate->sys_tracefunc,
|
||||
&f->f_trace, f, str_call,
|
||||
Py_None/*XXX how to compute arguments now?*/)) {
|
||||
if (call_trace(tstate->c_tracefunc, tstate->c_traceobj,
|
||||
f, PyTrace_CALL, Py_None)) {
|
||||
/* XXX Need way to compute arguments?? */
|
||||
/* Trace function raised an error */
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
if (tstate->sys_profilefunc != NULL) {
|
||||
if (tstate->c_profilefunc != NULL) {
|
||||
/* Similar for sys_profilefunc, except it needn't return
|
||||
itself and isn't called for "line" events */
|
||||
if (call_trace(&tstate->sys_profilefunc,
|
||||
(PyObject**)0, f, str_call,
|
||||
Py_None/*XXX*/)) {
|
||||
if (call_trace(tstate->c_profilefunc, tstate->c_profileobj,
|
||||
f, PyTrace_CALL, Py_None)) {
|
||||
/* XXX Need way to compute arguments?? */
|
||||
/* Profile function raised an error */
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
@ -2772,7 +2769,7 @@ prtrace(PyObject *v, char *str)
|
|||
#endif
|
||||
|
||||
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;
|
||||
int err;
|
||||
|
@ -2786,7 +2783,7 @@ call_exc_trace(PyObject **p_trace, PyObject **p_newtrace, PyFrameObject *f)
|
|||
PyErr_Restore(type, value, traceback);
|
||||
return;
|
||||
}
|
||||
err = call_trace(p_trace, p_newtrace, f, str_exception, arg);
|
||||
err = call_trace(func, self, f, PyTrace_EXCEPTION, arg);
|
||||
Py_DECREF(arg);
|
||||
if (err == 0)
|
||||
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
|
||||
call_trace(PyObject **p_trace, PyObject **p_newtrace, PyFrameObject *f,
|
||||
PyObject *msg, PyObject *arg)
|
||||
call_trace(Py_tracefunc func, PyObject *obj, PyFrameObject *frame,
|
||||
int what, PyObject *arg)
|
||||
{
|
||||
PyThreadState *tstate = f->f_tstate;
|
||||
PyObject *args;
|
||||
PyObject *res = NULL;
|
||||
|
||||
if (tstate->tracing) {
|
||||
/* Don't do recursive traces */
|
||||
if (p_newtrace) {
|
||||
Py_XDECREF(*p_newtrace);
|
||||
*p_newtrace = NULL;
|
||||
}
|
||||
register PyThreadState *tstate = frame->f_tstate;
|
||||
int result;
|
||||
if (tstate->tracing)
|
||||
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++;
|
||||
PyFrame_FastToLocals(f);
|
||||
res = PyEval_CallObject(*p_trace, args); /* May clear *p_trace! */
|
||||
PyFrame_LocalsToFast(f, 1);
|
||||
result = func(obj, frame, what, arg);
|
||||
tstate->tracing--;
|
||||
cleanup:
|
||||
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;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/* Initialize the strings that get passed to the profile and trace functions;
|
||||
* this avoids doing this while we're actually profiling/tracing.
|
||||
*/
|
||||
int
|
||||
_PyTrace_Init(void)
|
||||
void
|
||||
PyEval_SetProfile(Py_tracefunc func, PyObject *arg)
|
||||
{
|
||||
if (str_call == NULL) {
|
||||
str_call = PyString_InternFromString("call");
|
||||
if (str_call == NULL)
|
||||
return -1;
|
||||
}
|
||||
if (str_exception == NULL) {
|
||||
str_exception = PyString_InternFromString("exception");
|
||||
if (str_exception == NULL)
|
||||
return -1;
|
||||
}
|
||||
if (str_line == NULL) {
|
||||
str_line = PyString_InternFromString("line");
|
||||
if (str_line == NULL)
|
||||
return -1;
|
||||
}
|
||||
if (str_return == NULL) {
|
||||
str_return = PyString_InternFromString("return");
|
||||
if (str_return == NULL)
|
||||
return -1;
|
||||
}
|
||||
return 0;
|
||||
PyThreadState *tstate = PyThreadState_Get();
|
||||
PyObject *temp = tstate->c_profileobj;
|
||||
Py_XINCREF(arg);
|
||||
tstate->c_profilefunc = NULL;
|
||||
tstate->c_profileobj = NULL;
|
||||
Py_XDECREF(temp);
|
||||
tstate->c_profilefunc = func;
|
||||
tstate->c_profileobj = arg;
|
||||
}
|
||||
|
||||
void
|
||||
PyEval_SetTrace(Py_tracefunc func, PyObject *arg)
|
||||
{
|
||||
PyThreadState *tstate = PyThreadState_Get();
|
||||
PyObject *temp = tstate->c_traceobj;
|
||||
Py_XINCREF(arg);
|
||||
tstate->c_tracefunc = NULL;
|
||||
tstate->c_traceobj = NULL;
|
||||
Py_XDECREF(temp);
|
||||
tstate->c_tracefunc = func;
|
||||
tstate->c_traceobj = arg;
|
||||
}
|
||||
|
||||
PyObject *
|
||||
|
|
|
@ -120,8 +120,10 @@ PyThreadState_New(PyInterpreterState *interp)
|
|||
tstate->exc_value = NULL;
|
||||
tstate->exc_traceback = NULL;
|
||||
|
||||
tstate->sys_profilefunc = NULL;
|
||||
tstate->sys_tracefunc = NULL;
|
||||
tstate->c_profilefunc = NULL;
|
||||
tstate->c_tracefunc = NULL;
|
||||
tstate->c_profileobj = NULL;
|
||||
tstate->c_traceobj = NULL;
|
||||
|
||||
HEAD_LOCK();
|
||||
tstate->next = interp->tstate_head;
|
||||
|
@ -152,8 +154,10 @@ PyThreadState_Clear(PyThreadState *tstate)
|
|||
ZAP(tstate->exc_value);
|
||||
ZAP(tstate->exc_traceback);
|
||||
|
||||
ZAP(tstate->sys_profilefunc);
|
||||
ZAP(tstate->sys_tracefunc);
|
||||
tstate->c_profilefunc = NULL;
|
||||
tstate->c_tracefunc = NULL;
|
||||
ZAP(tstate->c_profileobj);
|
||||
ZAP(tstate->c_traceobj);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -196,20 +196,120 @@ static char setdefaultencoding_doc[] =
|
|||
\n\
|
||||
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 *
|
||||
sys_settrace(PyObject *self, PyObject *args)
|
||||
{
|
||||
PyThreadState *tstate = PyThreadState_Get();
|
||||
if (_PyTrace_Init() == -1)
|
||||
if (trace_init() == -1)
|
||||
return NULL;
|
||||
if (args == Py_None)
|
||||
args = NULL;
|
||||
PyEval_SetTrace(NULL, NULL);
|
||||
else
|
||||
Py_XINCREF(args);
|
||||
Py_XDECREF(tstate->sys_tracefunc);
|
||||
tstate->sys_tracefunc = args;
|
||||
PyEval_SetTrace(trace_trampoline, args);
|
||||
Py_INCREF(Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
|
@ -223,15 +323,12 @@ function call. See the debugger chapter in the library manual.";
|
|||
static PyObject *
|
||||
sys_setprofile(PyObject *self, PyObject *args)
|
||||
{
|
||||
PyThreadState *tstate = PyThreadState_Get();
|
||||
if (_PyTrace_Init() == -1)
|
||||
if (trace_init() == -1)
|
||||
return NULL;
|
||||
if (args == Py_None)
|
||||
args = NULL;
|
||||
PyEval_SetProfile(NULL, NULL);
|
||||
else
|
||||
Py_XINCREF(args);
|
||||
Py_XDECREF(tstate->sys_profilefunc);
|
||||
tstate->sys_profilefunc = args;
|
||||
PyEval_SetProfile(profile_trampoline, args);
|
||||
Py_INCREF(Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue