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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy