]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-103533: Use PEP 669 APIs for cprofile (GH-103534)
authorTian Gao <gaogaotiantian@hotmail.com>
Fri, 5 May 2023 17:38:47 +0000 (10:38 -0700)
committerGitHub <noreply@github.com>
Fri, 5 May 2023 17:38:47 +0000 (18:38 +0100)
Lib/test/test_cprofile.py
Misc/NEWS.d/next/Library/2023-04-14-06-32-54.gh-issue-103533.n_AfcS.rst [new file with mode: 0644]
Modules/_lsprof.c
Tools/c-analyzer/cpython/ignored.tsv

index 98648528bc81f23421bb2e4cbd32ebb499dab90f..484b8f8e3a365c40334fa60c6e4ec5eb7ab68156 100644 (file)
@@ -25,7 +25,6 @@ class CProfileTest(ProfileTest):
         with support.catch_unraisable_exception() as cm:
             obj = _lsprof.Profiler(lambda: int)
             obj.enable()
-            obj = _lsprof.Profiler(1)
             obj.disable()
             obj.clear()
 
@@ -37,10 +36,11 @@ class CProfileTest(ProfileTest):
         self.addCleanup(prof.disable)
 
         prof.enable()
-        self.assertIs(sys.getprofile(), prof)
+        self.assertEqual(
+            sys.monitoring.get_tool(sys.monitoring.PROFILER_ID), "cProfile")
 
         prof.disable()
-        self.assertIs(sys.getprofile(), None)
+        self.assertIs(sys.monitoring.get_tool(sys.monitoring.PROFILER_ID), None)
 
     def test_profile_as_context_manager(self):
         prof = self.profilerclass()
@@ -53,10 +53,19 @@ class CProfileTest(ProfileTest):
 
             # profile should be set as the global profiler inside the
             # with-block
-            self.assertIs(sys.getprofile(), prof)
+            self.assertEqual(
+                sys.monitoring.get_tool(sys.monitoring.PROFILER_ID), "cProfile")
 
         # profile shouldn't be set once we leave the with-block.
-        self.assertIs(sys.getprofile(), None)
+        self.assertIs(sys.monitoring.get_tool(sys.monitoring.PROFILER_ID), None)
+
+    def test_second_profiler(self):
+        pr = self.profilerclass()
+        pr2 = self.profilerclass()
+        pr.enable()
+        self.assertRaises(ValueError, pr2.enable)
+        pr.disable()
+
 
 class TestCommandLine(unittest.TestCase):
     def test_sort(self):
diff --git a/Misc/NEWS.d/next/Library/2023-04-14-06-32-54.gh-issue-103533.n_AfcS.rst b/Misc/NEWS.d/next/Library/2023-04-14-06-32-54.gh-issue-103533.n_AfcS.rst
new file mode 100644 (file)
index 0000000..1008ea0
--- /dev/null
@@ -0,0 +1 @@
+Update :mod:`cProfile` to use PEP 669 API
index 83d034ae7eed78d57ce354ab22c431f31c52d49c..a7ce328cb5307a2d797e53813d4e8a5dfa27ce4f 100644 (file)
@@ -49,6 +49,8 @@ typedef struct {
     int flags;
     PyObject *externalTimer;
     double externalTimerUnit;
+    int tool_id;
+    PyObject* missing;
 } ProfilerObject;
 
 #define POF_ENABLED     0x001
@@ -399,64 +401,6 @@ ptrace_leave_call(PyObject *self, void *key)
     pObj->freelistProfilerContext = pContext;
 }
 
-static int
-profiler_callback(PyObject *self, PyFrameObject *frame, int what,
-                  PyObject *arg)
-{
-    switch (what) {
-
-    /* the 'frame' of a called function is about to start its execution */
-    case PyTrace_CALL:
-    {
-        PyCodeObject *code = PyFrame_GetCode(frame);
-        ptrace_enter_call(self, (void *)code, (PyObject *)code);
-        Py_DECREF(code);
-        break;
-    }
-
-    /* the 'frame' of a called function is about to finish
-       (either normally or with an exception) */
-    case PyTrace_RETURN:
-    {
-        PyCodeObject *code = PyFrame_GetCode(frame);
-        ptrace_leave_call(self, (void *)code);
-        Py_DECREF(code);
-        break;
-    }
-
-    /* case PyTrace_EXCEPTION:
-        If the exception results in the function exiting, a
-        PyTrace_RETURN event will be generated, so we don't need to
-        handle it. */
-
-    /* the Python function 'frame' is issuing a call to the built-in
-       function 'arg' */
-    case PyTrace_C_CALL:
-        if ((((ProfilerObject *)self)->flags & POF_BUILTINS)
-            && PyCFunction_Check(arg)) {
-            ptrace_enter_call(self,
-                              ((PyCFunctionObject *)arg)->m_ml,
-                              arg);
-        }
-        break;
-
-    /* the call to the built-in function 'arg' is returning into its
-       caller 'frame' */
-    case PyTrace_C_RETURN:              /* ...normally */
-    case PyTrace_C_EXCEPTION:           /* ...with an exception set */
-        if ((((ProfilerObject *)self)->flags & POF_BUILTINS)
-            && PyCFunction_Check(arg)) {
-            ptrace_leave_call(self,
-                              ((PyCFunctionObject *)arg)->m_ml);
-        }
-        break;
-
-    default:
-        break;
-    }
-    return 0;
-}
-
 static int
 pending_exception(ProfilerObject *pObj)
 {
@@ -650,6 +594,99 @@ setBuiltins(ProfilerObject *pObj, int nvalue)
     return 0;
 }
 
+PyObject* pystart_callback(ProfilerObject* self, PyObject *const *args, Py_ssize_t size)
+{
+    PyObject* code = args[0];
+    ptrace_enter_call((PyObject*)self, (void *)code, (PyObject *)code);
+
+    Py_RETURN_NONE;
+}
+
+PyObject* pyreturn_callback(ProfilerObject* self, PyObject *const *args, Py_ssize_t size)
+{
+    PyObject* code = args[0];
+    ptrace_leave_call((PyObject*)self, (void *)code);
+
+    Py_RETURN_NONE;
+}
+
+PyObject* get_cfunc_from_callable(PyObject* callable, PyObject* self_arg, PyObject* missing)
+{
+    // return a new reference
+    if (PyCFunction_Check(callable)) {
+        Py_INCREF(callable);
+        return (PyObject*)((PyCFunctionObject *)callable);
+    }
+    if (Py_TYPE(callable) == &PyMethodDescr_Type) {
+        /* For backwards compatibility need to
+         * convert to builtin method */
+
+        /* If no arg, skip */
+        if (self_arg == missing) {
+            return NULL;
+        }
+        PyObject *meth = Py_TYPE(callable)->tp_descr_get(
+            callable, self_arg, (PyObject*)Py_TYPE(self_arg));
+        if (meth == NULL) {
+            return NULL;
+        }
+        if (PyCFunction_Check(meth)) {
+            return (PyObject*)((PyCFunctionObject *)meth);
+        }
+    }
+    return NULL;
+}
+
+PyObject* ccall_callback(ProfilerObject* self, PyObject *const *args, Py_ssize_t size)
+{
+    if (self->flags & POF_BUILTINS) {
+        PyObject* callable = args[2];
+        PyObject* self_arg = args[3];
+
+        PyObject* cfunc = get_cfunc_from_callable(callable, self_arg, self->missing);
+
+        if (cfunc) {
+            ptrace_enter_call((PyObject*)self,
+                              ((PyCFunctionObject *)cfunc)->m_ml,
+                              cfunc);
+            Py_DECREF(cfunc);
+        }
+    }
+    Py_RETURN_NONE;
+}
+
+PyObject* creturn_callback(ProfilerObject* self, PyObject *const *args, Py_ssize_t size)
+{
+    if (self->flags & POF_BUILTINS) {
+        PyObject* callable = args[2];
+        PyObject* self_arg = args[3];
+
+        PyObject* cfunc = get_cfunc_from_callable(callable, self_arg, self->missing);
+
+        if (cfunc) {
+            ptrace_leave_call((PyObject*)self,
+                              ((PyCFunctionObject *)cfunc)->m_ml);
+            Py_DECREF(cfunc);
+        }
+    }
+    Py_RETURN_NONE;
+}
+
+static const struct {
+    int event;
+    const char* callback_method;
+} callback_table[] = {
+    {PY_MONITORING_EVENT_PY_START, "_pystart_callback"},
+    {PY_MONITORING_EVENT_PY_RESUME, "_pystart_callback"},
+    {PY_MONITORING_EVENT_PY_RETURN, "_pyreturn_callback"},
+    {PY_MONITORING_EVENT_PY_YIELD, "_pyreturn_callback"},
+    {PY_MONITORING_EVENT_PY_UNWIND, "_pyreturn_callback"},
+    {PY_MONITORING_EVENT_CALL, "_ccall_callback"},
+    {PY_MONITORING_EVENT_C_RETURN, "_creturn_callback"},
+    {PY_MONITORING_EVENT_C_RAISE, "_creturn_callback"},
+    {0, NULL}
+};
+
 PyDoc_STRVAR(enable_doc, "\
 enable(subcalls=True, builtins=True)\n\
 \n\
@@ -666,6 +703,8 @@ profiler_enable(ProfilerObject *self, PyObject *args, PyObject *kwds)
     int subcalls = -1;
     int builtins = -1;
     static char *kwlist[] = {"subcalls", "builtins", 0};
+    int all_events = 0;
+
     if (!PyArg_ParseTupleAndKeywords(args, kwds, "|pp:enable",
                                      kwlist, &subcalls, &builtins))
         return NULL;
@@ -673,11 +712,37 @@ profiler_enable(ProfilerObject *self, PyObject *args, PyObject *kwds)
         return NULL;
     }
 
-    PyThreadState *tstate = _PyThreadState_GET();
-    if (_PyEval_SetProfile(tstate, profiler_callback, (PyObject*)self) < 0) {
+    PyObject* monitoring = _PyImport_GetModuleAttrString("sys", "monitoring");
+    if (!monitoring) {
+        return NULL;
+    }
+
+    if (PyObject_CallMethod(monitoring, "use_tool_id", "is", self->tool_id, "cProfile") == NULL) {
+        PyErr_Format(PyExc_ValueError, "Another profiling tool is already active");
+        Py_DECREF(monitoring);
+        return NULL;
+    }
+
+    for (int i = 0; callback_table[i].callback_method; i++) {
+        PyObject* callback = PyObject_GetAttrString((PyObject*)self, callback_table[i].callback_method);
+        if (!callback) {
+            Py_DECREF(monitoring);
+            return NULL;
+        }
+        Py_XDECREF(PyObject_CallMethod(monitoring, "register_callback", "iiO", self->tool_id,
+                                       (1 << callback_table[i].event),
+                                       callback));
+        Py_DECREF(callback);
+        all_events |= (1 << callback_table[i].event);
+    }
+
+    if (!PyObject_CallMethod(monitoring, "set_events", "ii", self->tool_id, all_events)) {
+        Py_DECREF(monitoring);
         return NULL;
     }
 
+    Py_DECREF(monitoring);
+
     self->flags |= POF_ENABLED;
     Py_RETURN_NONE;
 }
@@ -707,13 +772,44 @@ Stop collecting profiling information.\n\
 static PyObject*
 profiler_disable(ProfilerObject *self, PyObject* noarg)
 {
-    PyThreadState *tstate = _PyThreadState_GET();
-    if (_PyEval_SetProfile(tstate, NULL, NULL) < 0) {
-        return NULL;
+    if (self->flags & POF_ENABLED) {
+        PyObject* result = NULL;
+        PyObject* monitoring = _PyImport_GetModuleAttrString("sys", "monitoring");
+
+        if (!monitoring) {
+            return NULL;
+        }
+
+        for (int i = 0; callback_table[i].callback_method; i++) {
+            result = PyObject_CallMethod(monitoring, "register_callback", "iiO", self->tool_id,
+                                         (1 << callback_table[i].event), Py_None);
+            if (!result) {
+                Py_DECREF(monitoring);
+                return NULL;
+            }
+            Py_DECREF(result);
+        }
+
+        result = PyObject_CallMethod(monitoring, "set_events", "ii", self->tool_id, 0);
+        if (!result) {
+            Py_DECREF(monitoring);
+            return NULL;
+        }
+        Py_DECREF(result);
+
+        result = PyObject_CallMethod(monitoring, "free_tool_id", "i", self->tool_id);
+        if (!result) {
+            Py_DECREF(monitoring);
+            return NULL;
+        }
+        Py_DECREF(result);
+
+        Py_DECREF(monitoring);
+
+        self->flags &= ~POF_ENABLED;
+        flush_unmatched(self);
     }
-    self->flags &= ~POF_ENABLED;
 
-    flush_unmatched(self);
     if (pending_exception(self)) {
         return NULL;
     }
@@ -778,17 +874,37 @@ profiler_init(ProfilerObject *pObj, PyObject *args, PyObject *kw)
         return -1;
     pObj->externalTimerUnit = timeunit;
     Py_XSETREF(pObj->externalTimer, Py_XNewRef(timer));
+    pObj->tool_id = PY_MONITORING_PROFILER_ID;
+
+    PyObject* monitoring = _PyImport_GetModuleAttrString("sys", "monitoring");
+    if (!monitoring) {
+        return -1;
+    }
+    pObj->missing = PyObject_GetAttrString(monitoring, "MISSING");
+    if (!pObj->missing) {
+        Py_DECREF(monitoring);
+        return -1;
+    }
+    Py_DECREF(monitoring);
     return 0;
 }
 
 static PyMethodDef profiler_methods[] = {
     _LSPROF_PROFILER_GETSTATS_METHODDEF
-    {"enable",          _PyCFunction_CAST(profiler_enable),
+    {"enable",             _PyCFunction_CAST(profiler_enable),
                     METH_VARARGS | METH_KEYWORDS,       enable_doc},
-    {"disable",         (PyCFunction)profiler_disable,
+    {"disable",            (PyCFunction)profiler_disable,
                     METH_NOARGS,                        disable_doc},
-    {"clear",           (PyCFunction)profiler_clear,
+    {"clear",              (PyCFunction)profiler_clear,
                     METH_NOARGS,                        clear_doc},
+    {"_pystart_callback",  _PyCFunction_CAST(pystart_callback),
+                    METH_FASTCALL,                       NULL},
+    {"_pyreturn_callback", _PyCFunction_CAST(pyreturn_callback),
+                    METH_FASTCALL,                       NULL},
+    {"_ccall_callback",    _PyCFunction_CAST(ccall_callback),
+                    METH_FASTCALL,                       NULL},
+    {"_creturn_callback", _PyCFunction_CAST(creturn_callback),
+                    METH_FASTCALL,                       NULL},
     {NULL, NULL}
 };
 
index fee493ff7f1666b366b667829de808c95c32da41..7ba116dcb171cf4116b43f55ea739823a5c115d8 100644 (file)
@@ -216,6 +216,7 @@ Modules/_io/_iomodule.c     -       static_types    -
 Modules/_io/textio.c   -       encodefuncs     -
 Modules/_io/winconsoleio.c     -       _PyWindowsConsoleIO_Type        -
 Modules/_localemodule.c        -       langinfo_constants      -
+Modules/_lsprof.c      -       callback_table  -
 Modules/_pickle.c      -       READ_WHOLE_LINE -
 Modules/_sqlite/module.c       -       error_codes     -
 Modules/_sre/sre.c     pattern_repr    flag_names      -