Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit b979741

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

File tree

4 files changed

+200
-73
lines changed
Filter options

4 files changed

+200
-73
lines changed

‎Lib/test/test_cprofile.py

Copy file name to clipboardExpand all lines: Lib/test/test_cprofile.py
+14-5Lines 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):
+1Lines 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

Copy file name to clipboardExpand all lines: Modules/_lsprof.c
+184-68Lines 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

Copy file name to clipboardExpand all lines: Tools/c-analyzer/cpython/ignored.tsv
+1Lines 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 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.