[pypy-commit] pypy vmprof-review: Enable(), disable()

arigo noreply at buildbot.pypy.org
Sun Aug 2 17:41:43 CEST 2015


Author: Armin Rigo <arigo at tunes.org>
Branch: vmprof-review
Changeset: r78745:6ea3a10ad04e
Date: 2015-08-02 17:41 +0200
http://bitbucket.org/pypy/pypy/changeset/6ea3a10ad04e/

Log:	Enable(), disable()

diff --git a/pypy/interpreter/baseobjspace.py b/pypy/interpreter/baseobjspace.py
--- a/pypy/interpreter/baseobjspace.py
+++ b/pypy/interpreter/baseobjspace.py
@@ -11,7 +11,7 @@
     INT_MIN, INT_MAX, UINT_MAX, USHRT_MAX
 
 from pypy.interpreter.executioncontext import (ExecutionContext, ActionFlag,
-    UserDelAction, CodeUniqueIds)
+    UserDelAction)
 from pypy.interpreter.error import OperationError, new_exception_class, oefmt
 from pypy.interpreter.argument import Arguments
 from pypy.interpreter.miscutils import ThreadLocals, make_weak_value_dictionary
@@ -391,7 +391,6 @@
         self.actionflag = ActionFlag()    # changed by the signal module
         self.check_signal_action = None   # changed by the signal module
         self.user_del_action = UserDelAction(self)
-        self.code_unique_ids = CodeUniqueIds()
         self._code_of_sys_exc_info = None
 
         # can be overridden to a subclass
@@ -670,16 +669,6 @@
             assert ec is not None
             return ec
 
-    def register_code_callback(self, callback):
-        cui = self.code_unique_ids
-        cui.code_callback = callback
-
-    def register_code_object(self, pycode):
-        cui = self.code_unique_ids
-        if cui.code_callback is None:
-            return
-        cui.code_callback(self, pycode)
-
     def _freeze_(self):
         return True
 
diff --git a/rpython/rlib/rvmprof/cintf.py b/rpython/rlib/rvmprof/cintf.py
--- a/rpython/rlib/rvmprof/cintf.py
+++ b/rpython/rlib/rvmprof/cintf.py
@@ -28,14 +28,13 @@
 
 vmprof_init = rffi.llexternal("rpython_vmprof_init", [], rffi.CCHARP,
                               compilation_info=eci)
-## vmprof_enable = rffi.llexternal("vmprof_enable",
-##                                 [rffi.INT, rffi.LONG, rffi.INT,
-##                                  rffi.CCHARP, rffi.INT],
-##                                 rffi.INT, compilation_info=eci,
-##                                 save_err=rffi.RFFI_SAVE_ERRNO)
-## vmprof_disable = rffi.llexternal("vmprof_disable", [], rffi.INT,
-##                                  compilation_info=eci,
-##                                 save_err=rffi.RFFI_SAVE_ERRNO)
+vmprof_enable = rffi.llexternal("rpython_vmprof_enable",
+                                [rffi.INT, rffi.LONG],
+                                rffi.INT, compilation_info=eci,
+                                save_err=rffi.RFFI_SAVE_ERRNO)
+vmprof_disable = rffi.llexternal("rpython_vmprof_disable", [], rffi.INT,
+                                 compilation_info=eci,
+                                 save_err=rffi.RFFI_SAVE_ERRNO)
 
 ## vmprof_register_virtual_function = rffi.llexternal(
 ##     "vmprof_register_virtual_function",
@@ -47,9 +46,6 @@
                                         compilation_info=eci)
 
 
-def vmprof_enable(fileno, interval_usec): return 0
-
-
 def token2lltype(tok):
     if tok == 'i':
         return lltype.Signed
diff --git a/rpython/rlib/rvmprof/rvmprof.py b/rpython/rlib/rvmprof/rvmprof.py
--- a/rpython/rlib/rvmprof/rvmprof.py
+++ b/rpython/rlib/rvmprof/rvmprof.py
@@ -84,18 +84,17 @@
     def enable(self, fileno, interval):
         """Enable vmprof.  Writes go to the given 'fileno'.
         The sampling interval is given by 'interval' as a number of
-        seconds, as a float which must be not greater than 1.0.
+        seconds, as a float which must be smaller than 1.0.
         Raises VMProfError if something goes wrong.
         """
         assert fileno >= 0
         if self.is_enabled:
             raise VMProfError("vmprof is already enabled")
-        if not (1e-6 <= interval <= 1.0):
+        if not (1e-6 <= interval < 1.0):
             raise VMProfError("bad value for 'interval'")
         interval_usec = int(interval * 1000000.0)
         #
         self.fileno = fileno
-        self.is_enabled = True
         self._write_header(interval_usec)
         if not self.ever_enabled:
             if we_are_translated():
@@ -109,6 +108,22 @@
             res = cintf.vmprof_enable(fileno, interval_usec)
             if res < 0:
                 raise VMProfError(os.strerror(rposix.get_saved_errno()))
+        self.is_enabled = True
+
+    def disable(self):
+        """Disable vmprof.
+        Raises VMProfError if something goes wrong.
+        """
+        if not self.is_enabled:
+            raise VMProfError("vmprof is not enabled")
+        self.is_enabled = False
+        self._flush_codes()
+        self.fileno = -1
+        if we_are_translated():
+           # does not work untranslated
+            res = cintf.vmprof_disable()
+            if res < 0:
+                raise VMProfError(os.strerror(rposix.get_saved_errno()))
 
     def _write_code_registration(self, uid, name):
         b = self._current_codes
diff --git a/rpython/rlib/rvmprof/src/rvmprof.c b/rpython/rlib/rvmprof/src/rvmprof.c
--- a/rpython/rlib/rvmprof/src/rvmprof.c
+++ b/rpython/rlib/rvmprof/src/rvmprof.c
@@ -19,6 +19,15 @@
 #include "rvmprof_getpc.h"
 #include "rvmprof_base.h"
 #include <dlfcn.h>
+#include <assert.h>
+#include <pthread.h>
+#include <sys/time.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
 
 
 /************************************************************/
@@ -56,7 +65,7 @@
 
 /************************************************************/
 
-static long volatile ignore_signals = 0;
+static long volatile ignore_signals = 1;
 
 RPY_EXTERN
 void rpython_vmprof_ignore_signals(int ignored)
@@ -70,3 +79,179 @@
     _InterlockedExchange(&ignore_signals, (long)ignored);
 #endif
 }
+
+
+/* *************************************************************
+ * functions to write a profile file compatible with gperftools
+ * *************************************************************
+ */
+
+#define MARKER_STACKTRACE '\x01'
+#define MARKER_VIRTUAL_IP '\x02'
+#define MARKER_TRAILER '\x03'
+
+static int profile_file;
+static long profile_interval_usec;
+static char atfork_hook_installed = 0;
+
+static int _write_all(const void *buf, size_t bufsize)
+{
+    while (bufsize > 0) {
+        ssize_t count = write(profile_file, buf, bufsize);
+        if (count <= 0)
+            return -1;   /* failed */
+        buf += count;
+        bufsize -= count;
+    }
+    return 0;
+}
+
+static void sigprof_handler(int sig_nr, siginfo_t* info, void *ucontext) {
+    int saved_errno = errno;
+    /*
+    void* stack[MAX_STACK_DEPTH];
+    stack[0] = GetPC((ucontext_t*)ucontext);
+    int depth = frame_forcer(get_stack_trace(stack+1, MAX_STACK_DEPTH-1, ucontext));
+    depth++;  // To account for pc value in stack[0];
+    prof_write_stacktrace(stack, depth, 1);
+    */
+    errno = saved_errno;
+}
+
+
+/************************************************************/
+
+static int install_sigprof_handler(void)
+{
+    struct sigaction sa;
+    memset(&sa, 0, sizeof(sa));
+    sa.sa_sigaction = sigprof_handler;
+    sa.sa_flags = SA_RESTART | SA_SIGINFO;
+    if (sigemptyset(&sa.sa_mask) == -1 ||
+        sigaction(SIGPROF, &sa, NULL) == -1)
+        return -1;
+    return 0;
+}
+
+static int remove_sigprof_handler(void)
+{
+    sighandler_t res = signal(SIGPROF, SIG_DFL);
+    if (res == SIG_ERR)
+        return -1;
+    return 0;
+}
+
+static int install_sigprof_timer(void)
+{
+    static struct itimerval timer;
+    timer.it_interval.tv_sec = 0;
+    timer.it_interval.tv_usec = profile_interval_usec;
+    timer.it_value = timer.it_interval;
+    if (setitimer(ITIMER_PROF, &timer, NULL) != 0)
+        return -1;
+    return 0;
+}
+
+static int remove_sigprof_timer(void) {
+    static struct itimerval timer;
+    timer.it_interval.tv_sec = 0;
+    timer.it_interval.tv_usec = 0;
+    timer.it_value.tv_sec = 0;
+    timer.it_value.tv_usec = 0;
+    if (setitimer(ITIMER_PROF, &timer, NULL) != 0)
+        return -1;
+    return 0;
+}
+
+static void atfork_disable_timer(void) {
+    if (profile_interval_usec > 0) {
+        remove_sigprof_timer();
+    }
+}
+
+static void atfork_enable_timer(void) {
+    if (profile_interval_usec > 0) {
+        install_sigprof_timer();
+    }
+}
+
+static int install_pthread_atfork_hooks(void) {
+    /* this is needed to prevent the problems described there:
+         - http://code.google.com/p/gperftools/issues/detail?id=278
+         - http://lists.debian.org/debian-glibc/2010/03/msg00161.html
+
+        TL;DR: if the RSS of the process is large enough, the clone() syscall
+        will be interrupted by the SIGPROF before it can complete, then
+        retried, interrupted again and so on, in an endless loop.  The
+        solution is to disable the timer around the fork, and re-enable it
+        only inside the parent.
+    */
+    if (atfork_hook_installed)
+        return 0;
+    int ret = pthread_atfork(atfork_disable_timer, atfork_enable_timer, NULL);
+    if (ret != 0)
+        return -1;
+    atfork_hook_installed = 1;
+    return 0;
+}
+
+RPY_EXTERN
+int rpython_vmprof_enable(int fd, long interval_usec)
+{
+    assert(fd >= 0);
+    assert(interval_usec > 0);
+    profile_file = fd;
+    profile_interval_usec = interval_usec;
+
+    if (install_pthread_atfork_hooks() == -1)
+        return -1;
+    if (install_sigprof_handler() == -1)
+        return -1;
+    if (install_sigprof_timer() == -1)
+        return -1;
+    rpython_vmprof_ignore_signals(0);
+    return 0;
+}
+
+RPY_EXTERN
+int rpython_vmprof_disable(void)
+{
+    int srcfd;
+    char buf[4096];
+    ssize_t size;
+    unsigned char marker = MARKER_TRAILER;
+
+    rpython_vmprof_ignore_signals(1);
+    profile_interval_usec = 0;
+
+    if (_write_all(&marker, 1) < 0)
+        return -1;
+
+#ifdef __linux__
+    // copy /proc/PID/maps to the end of the profile file
+    sprintf(buf, "/proc/%d/maps", getpid());
+    srcfd = open(buf, O_RDONLY);
+    if (srcfd < 0)
+        return -1;
+
+    while ((size = read(srcfd, buf, sizeof buf)) > 0) {
+        _write_all(buf, size);
+    }
+    close(srcfd);
+#else
+    // freebsd and mac
+#   error "REVIEW AND FIX ME"
+    sprintf(buf, "procstat -v %d", getpid());
+    src = popen(buf, "r");
+    if (!src) {
+        vmprof_error = "error calling procstat";
+        return -1;
+    }
+    while ((size = fread(buf, 1, sizeof buf, src))) {
+        write(profile_file, buf, size);
+    }
+    pclose(src);
+#endif
+
+    return 0;
+}
diff --git a/rpython/rlib/rvmprof/src/rvmprof.h b/rpython/rlib/rvmprof/src/rvmprof.h
--- a/rpython/rlib/rvmprof/src/rvmprof.h
+++ b/rpython/rlib/rvmprof/src/rvmprof.h
@@ -1,3 +1,5 @@
 
 RPY_EXTERN char *rpython_vmprof_init(void);
 RPY_EXTERN void vmprof_ignore_signals(int);
+RPY_EXTERN int rpython_vmprof_enable(int, long);
+RPY_EXTERN int rpython_vmprof_disable(void);
diff --git a/rpython/rlib/rvmprof/test/test_rvmprof.py b/rpython/rlib/rvmprof/test/test_rvmprof.py
--- a/rpython/rlib/rvmprof/test/test_rvmprof.py
+++ b/rpython/rlib/rvmprof/test/test_rvmprof.py
@@ -96,6 +96,8 @@
         get_vmprof().enable(fd, 0.5)
         res = main(code, 5)
         assert res == 42
+        get_vmprof().disable()
+        os.close(fd)
         return 0
 
     assert f() == 0


More information about the pypy-commit mailing list