Skip to content

Commit b979741

Browse files
gh-103533: Use PEP 669 APIs for cprofile (GH-103534)
1 parent a0df9ee commit b979741

File tree

4 files changed

+200
-73
lines changed

4 files changed

+200
-73
lines changed

Lib/test/test_cprofile.py

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ def test_bad_counter_during_dealloc(self):
2525
with support.catch_unraisable_exception() as cm:
2626
obj = _lsprof.Profiler(lambda: int)
2727
obj.enable()
28-
obj = _lsprof.Profiler(1)
2928
obj.disable()
3029
obj.clear()
3130

@@ -37,10 +36,11 @@ def test_profile_enable_disable(self):
3736
self.addCleanup(prof.disable)
3837

3938
prof.enable()
40-
self.assertIs(sys.getprofile(), prof)
39+
self.assertEqual(
40+
sys.monitoring.get_tool(sys.monitoring.PROFILER_ID), "cProfile")
4141

4242
prof.disable()
43-
self.assertIs(sys.getprofile(), None)
43+
self.assertIs(sys.monitoring.get_tool(sys.monitoring.PROFILER_ID), None)
4444

4545
def test_profile_as_context_manager(self):
4646
prof = self.profilerclass()
@@ -53,10 +53,19 @@ def test_profile_as_context_manager(self):
5353

5454
# profile should be set as the global profiler inside the
5555
# with-block
56-
self.assertIs(sys.getprofile(), prof)
56+
self.assertEqual(
57+
sys.monitoring.get_tool(sys.monitoring.PROFILER_ID), "cProfile")
5758

5859
# profile shouldn't be set once we leave the with-block.
59-
self.assertIs(sys.getprofile(), None)
60+
self.assertIs(sys.monitoring.get_tool(sys.monitoring.PROFILER_ID), None)
61+
62+
def test_second_profiler(self):
63+
pr = self.profilerclass()
64+
pr2 = self.profilerclass()
65+
pr.enable()
66+
self.assertRaises(ValueError, pr2.enable)
67+
pr.disable()
68+
6069

6170
class TestCommandLine(unittest.TestCase):
6271
def test_sort(self):
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Update :mod:`cProfile` to use PEP 669 API

Modules/_lsprof.c

Lines changed: 184 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ typedef struct {
4949
int flags;
5050
PyObject *externalTimer;
5151
double externalTimerUnit;
52+
int tool_id;
53+
PyObject* missing;
5254
} ProfilerObject;
5355

5456
#define POF_ENABLED 0x001
@@ -399,64 +401,6 @@ ptrace_leave_call(PyObject *self, void *key)
399401
pObj->freelistProfilerContext = pContext;
400402
}
401403

402-
static int
403-
profiler_callback(PyObject *self, PyFrameObject *frame, int what,
404-
PyObject *arg)
405-
{
406-
switch (what) {
407-
408-
/* the 'frame' of a called function is about to start its execution */
409-
case PyTrace_CALL:
410-
{
411-
PyCodeObject *code = PyFrame_GetCode(frame);
412-
ptrace_enter_call(self, (void *)code, (PyObject *)code);
413-
Py_DECREF(code);
414-
break;
415-
}
416-
417-
/* the 'frame' of a called function is about to finish
418-
(either normally or with an exception) */
419-
case PyTrace_RETURN:
420-
{
421-
PyCodeObject *code = PyFrame_GetCode(frame);
422-
ptrace_leave_call(self, (void *)code);
423-
Py_DECREF(code);
424-
break;
425-
}
426-
427-
/* case PyTrace_EXCEPTION:
428-
If the exception results in the function exiting, a
429-
PyTrace_RETURN event will be generated, so we don't need to
430-
handle it. */
431-
432-
/* the Python function 'frame' is issuing a call to the built-in
433-
function 'arg' */
434-
case PyTrace_C_CALL:
435-
if ((((ProfilerObject *)self)->flags & POF_BUILTINS)
436-
&& PyCFunction_Check(arg)) {
437-
ptrace_enter_call(self,
438-
((PyCFunctionObject *)arg)->m_ml,
439-
arg);
440-
}
441-
break;
442-
443-
/* the call to the built-in function 'arg' is returning into its
444-
caller 'frame' */
445-
case PyTrace_C_RETURN: /* ...normally */
446-
case PyTrace_C_EXCEPTION: /* ...with an exception set */
447-
if ((((ProfilerObject *)self)->flags & POF_BUILTINS)
448-
&& PyCFunction_Check(arg)) {
449-
ptrace_leave_call(self,
450-
((PyCFunctionObject *)arg)->m_ml);
451-
}
452-
break;
453-
454-
default:
455-
break;
456-
}
457-
return 0;
458-
}
459-
460404
static int
461405
pending_exception(ProfilerObject *pObj)
462406
{
@@ -650,6 +594,99 @@ setBuiltins(ProfilerObject *pObj, int nvalue)
650594
return 0;
651595
}
652596

597+
PyObject* pystart_callback(ProfilerObject* self, PyObject *const *args, Py_ssize_t size)
598+
{
599+
PyObject* code = args[0];
600+
ptrace_enter_call((PyObject*)self, (void *)code, (PyObject *)code);
601+
602+
Py_RETURN_NONE;
603+
}
604+
605+
PyObject* pyreturn_callback(ProfilerObject* self, PyObject *const *args, Py_ssize_t size)
606+
{
607+
PyObject* code = args[0];
608+
ptrace_leave_call((PyObject*)self, (void *)code);
609+
610+
Py_RETURN_NONE;
611+
}
612+
613+
PyObject* get_cfunc_from_callable(PyObject* callable, PyObject* self_arg, PyObject* missing)
614+
{
615+
// return a new reference
616+
if (PyCFunction_Check(callable)) {
617+
Py_INCREF(callable);
618+
return (PyObject*)((PyCFunctionObject *)callable);
619+
}
620+
if (Py_TYPE(callable) == &PyMethodDescr_Type) {
621+
/* For backwards compatibility need to
622+
* convert to builtin method */
623+
624+
/* If no arg, skip */
625+
if (self_arg == missing) {
626+
return NULL;
627+
}
628+
PyObject *meth = Py_TYPE(callable)->tp_descr_get(
629+
callable, self_arg, (PyObject*)Py_TYPE(self_arg));
630+
if (meth == NULL) {
631+
return NULL;
632+
}
633+
if (PyCFunction_Check(meth)) {
634+
return (PyObject*)((PyCFunctionObject *)meth);
635+
}
636+
}
637+
return NULL;
638+
}
639+
640+
PyObject* ccall_callback(ProfilerObject* self, PyObject *const *args, Py_ssize_t size)
641+
{
642+
if (self->flags & POF_BUILTINS) {
643+
PyObject* callable = args[2];
644+
PyObject* self_arg = args[3];
645+
646+
PyObject* cfunc = get_cfunc_from_callable(callable, self_arg, self->missing);
647+
648+
if (cfunc) {
649+
ptrace_enter_call((PyObject*)self,
650+
((PyCFunctionObject *)cfunc)->m_ml,
651+
cfunc);
652+
Py_DECREF(cfunc);
653+
}
654+
}
655+
Py_RETURN_NONE;
656+
}
657+
658+
PyObject* creturn_callback(ProfilerObject* self, PyObject *const *args, Py_ssize_t size)
659+
{
660+
if (self->flags & POF_BUILTINS) {
661+
PyObject* callable = args[2];
662+
PyObject* self_arg = args[3];
663+
664+
PyObject* cfunc = get_cfunc_from_callable(callable, self_arg, self->missing);
665+
666+
if (cfunc) {
667+
ptrace_leave_call((PyObject*)self,
668+
((PyCFunctionObject *)cfunc)->m_ml);
669+
Py_DECREF(cfunc);
670+
}
671+
}
672+
Py_RETURN_NONE;
673+
}
674+
675+
static const struct {
676+
int event;
677+
const char* callback_method;
678+
} callback_table[] = {
679+
{PY_MONITORING_EVENT_PY_START, "_pystart_callback"},
680+
{PY_MONITORING_EVENT_PY_RESUME, "_pystart_callback"},
681+
{PY_MONITORING_EVENT_PY_RETURN, "_pyreturn_callback"},
682+
{PY_MONITORING_EVENT_PY_YIELD, "_pyreturn_callback"},
683+
{PY_MONITORING_EVENT_PY_UNWIND, "_pyreturn_callback"},
684+
{PY_MONITORING_EVENT_CALL, "_ccall_callback"},
685+
{PY_MONITORING_EVENT_C_RETURN, "_creturn_callback"},
686+
{PY_MONITORING_EVENT_C_RAISE, "_creturn_callback"},
687+
{0, NULL}
688+
};
689+
653690
PyDoc_STRVAR(enable_doc, "\
654691
enable(subcalls=True, builtins=True)\n\
655692
\n\
@@ -666,18 +703,46 @@ profiler_enable(ProfilerObject *self, PyObject *args, PyObject *kwds)
666703
int subcalls = -1;
667704
int builtins = -1;
668705
static char *kwlist[] = {"subcalls", "builtins", 0};
706+
int all_events = 0;
707+
669708
if (!PyArg_ParseTupleAndKeywords(args, kwds, "|pp:enable",
670709
kwlist, &subcalls, &builtins))
671710
return NULL;
672711
if (setSubcalls(self, subcalls) < 0 || setBuiltins(self, builtins) < 0) {
673712
return NULL;
674713
}
675714

676-
PyThreadState *tstate = _PyThreadState_GET();
677-
if (_PyEval_SetProfile(tstate, profiler_callback, (PyObject*)self) < 0) {
715+
PyObject* monitoring = _PyImport_GetModuleAttrString("sys", "monitoring");
716+
if (!monitoring) {
717+
return NULL;
718+
}
719+
720+
if (PyObject_CallMethod(monitoring, "use_tool_id", "is", self->tool_id, "cProfile") == NULL) {
721+
PyErr_Format(PyExc_ValueError, "Another profiling tool is already active");
722+
Py_DECREF(monitoring);
723+
return NULL;
724+
}
725+
726+
for (int i = 0; callback_table[i].callback_method; i++) {
727+
PyObject* callback = PyObject_GetAttrString((PyObject*)self, callback_table[i].callback_method);
728+
if (!callback) {
729+
Py_DECREF(monitoring);
730+
return NULL;
731+
}
732+
Py_XDECREF(PyObject_CallMethod(monitoring, "register_callback", "iiO", self->tool_id,
733+
(1 << callback_table[i].event),
734+
callback));
735+
Py_DECREF(callback);
736+
all_events |= (1 << callback_table[i].event);
737+
}
738+
739+
if (!PyObject_CallMethod(monitoring, "set_events", "ii", self->tool_id, all_events)) {
740+
Py_DECREF(monitoring);
678741
return NULL;
679742
}
680743

744+
Py_DECREF(monitoring);
745+
681746
self->flags |= POF_ENABLED;
682747
Py_RETURN_NONE;
683748
}
@@ -707,13 +772,44 @@ Stop collecting profiling information.\n\
707772
static PyObject*
708773
profiler_disable(ProfilerObject *self, PyObject* noarg)
709774
{
710-
PyThreadState *tstate = _PyThreadState_GET();
711-
if (_PyEval_SetProfile(tstate, NULL, NULL) < 0) {
712-
return NULL;
775+
if (self->flags & POF_ENABLED) {
776+
PyObject* result = NULL;
777+
PyObject* monitoring = _PyImport_GetModuleAttrString("sys", "monitoring");
778+
779+
if (!monitoring) {
780+
return NULL;
781+
}
782+
783+
for (int i = 0; callback_table[i].callback_method; i++) {
784+
result = PyObject_CallMethod(monitoring, "register_callback", "iiO", self->tool_id,
785+
(1 << callback_table[i].event), Py_None);
786+
if (!result) {
787+
Py_DECREF(monitoring);
788+
return NULL;
789+
}
790+
Py_DECREF(result);
791+
}
792+
793+
result = PyObject_CallMethod(monitoring, "set_events", "ii", self->tool_id, 0);
794+
if (!result) {
795+
Py_DECREF(monitoring);
796+
return NULL;
797+
}
798+
Py_DECREF(result);
799+
800+
result = PyObject_CallMethod(monitoring, "free_tool_id", "i", self->tool_id);
801+
if (!result) {
802+
Py_DECREF(monitoring);
803+
return NULL;
804+
}
805+
Py_DECREF(result);
806+
807+
Py_DECREF(monitoring);
808+
809+
self->flags &= ~POF_ENABLED;
810+
flush_unmatched(self);
713811
}
714-
self->flags &= ~POF_ENABLED;
715812

716-
flush_unmatched(self);
717813
if (pending_exception(self)) {
718814
return NULL;
719815
}
@@ -778,17 +874,37 @@ profiler_init(ProfilerObject *pObj, PyObject *args, PyObject *kw)
778874
return -1;
779875
pObj->externalTimerUnit = timeunit;
780876
Py_XSETREF(pObj->externalTimer, Py_XNewRef(timer));
877+
pObj->tool_id = PY_MONITORING_PROFILER_ID;
878+
879+
PyObject* monitoring = _PyImport_GetModuleAttrString("sys", "monitoring");
880+
if (!monitoring) {
881+
return -1;
882+
}
883+
pObj->missing = PyObject_GetAttrString(monitoring, "MISSING");
884+
if (!pObj->missing) {
885+
Py_DECREF(monitoring);
886+
return -1;
887+
}
888+
Py_DECREF(monitoring);
781889
return 0;
782890
}
783891

784892
static PyMethodDef profiler_methods[] = {
785893
_LSPROF_PROFILER_GETSTATS_METHODDEF
786-
{"enable", _PyCFunction_CAST(profiler_enable),
894+
{"enable", _PyCFunction_CAST(profiler_enable),
787895
METH_VARARGS | METH_KEYWORDS, enable_doc},
788-
{"disable", (PyCFunction)profiler_disable,
896+
{"disable", (PyCFunction)profiler_disable,
789897
METH_NOARGS, disable_doc},
790-
{"clear", (PyCFunction)profiler_clear,
898+
{"clear", (PyCFunction)profiler_clear,
791899
METH_NOARGS, clear_doc},
900+
{"_pystart_callback", _PyCFunction_CAST(pystart_callback),
901+
METH_FASTCALL, NULL},
902+
{"_pyreturn_callback", _PyCFunction_CAST(pyreturn_callback),
903+
METH_FASTCALL, NULL},
904+
{"_ccall_callback", _PyCFunction_CAST(ccall_callback),
905+
METH_FASTCALL, NULL},
906+
{"_creturn_callback", _PyCFunction_CAST(creturn_callback),
907+
METH_FASTCALL, NULL},
792908
{NULL, NULL}
793909
};
794910

Tools/c-analyzer/cpython/ignored.tsv

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ Modules/_io/_iomodule.c - static_types -
216216
Modules/_io/textio.c - encodefuncs -
217217
Modules/_io/winconsoleio.c - _PyWindowsConsoleIO_Type -
218218
Modules/_localemodule.c - langinfo_constants -
219+
Modules/_lsprof.c - callback_table -
219220
Modules/_pickle.c - READ_WHOLE_LINE -
220221
Modules/_sqlite/module.c - error_codes -
221222
Modules/_sre/sre.c pattern_repr flag_names -

0 commit comments

Comments
 (0)