Author: Maciej Fijalkowski <[email protected]>
Branch: vmprof
Changeset: r75508:a9901f5effb2
Date: 2015-01-24 14:00 +0200
http://bitbucket.org/pypy/pypy/changeset/a9901f5effb2/

Log:    Getting somewhere - a more sane way to record all the code objs and
        store them in the same file

diff --git a/pypy/interpreter/baseobjspace.py b/pypy/interpreter/baseobjspace.py
--- a/pypy/interpreter/baseobjspace.py
+++ b/pypy/interpreter/baseobjspace.py
@@ -9,6 +9,7 @@
 from rpython.rlib.signature import signature
 from rpython.rlib.rarithmetic import r_uint, SHRT_MIN, SHRT_MAX, \
     INT_MIN, INT_MAX, UINT_MAX, USHRT_MAX
+from rpython.rlib.rweaklist import RWeakListMixin
 
 from pypy.interpreter.executioncontext import (ExecutionContext, ActionFlag,
     UserDelAction)
@@ -366,6 +367,10 @@
 
 # ____________________________________________________________
 
+class CodeObjWeakList(RWeakListMixin):
+    def __init__(self):
+        self.initialize()
+
 class ObjSpace(object):
     """Base class for the interpreter-level implementations of object spaces.
     http://pypy.readthedocs.org/en/latest/objspace.html""";
@@ -389,6 +394,7 @@
         self.check_signal_action = None   # changed by the signal module
         self.user_del_action = UserDelAction(self)
         self._code_of_sys_exc_info = None
+        self.all_code_objs = CodeObjWeakList()
 
         # can be overridden to a subclass
         self.initialize()
@@ -666,6 +672,16 @@
             assert ec is not None
             return ec
 
+    def register_code_object(self, pycode):
+        callback = self.getexecutioncontext().register_code_callback
+        if callback is not None:
+            callback(self, pycode)
+        self.all_code_objs.add_handle(pycode)
+
+    def set_code_callback(self, callback):
+        ec = self.getexecutioncontext()
+        ec.register_code_callback = callback
+        
     def _freeze_(self):
         return True
 
diff --git a/pypy/interpreter/executioncontext.py 
b/pypy/interpreter/executioncontext.py
--- a/pypy/interpreter/executioncontext.py
+++ b/pypy/interpreter/executioncontext.py
@@ -33,6 +33,7 @@
         self.profilefunc = None
         self.w_profilefuncarg = None
         self.thread_disappeared = False   # might be set to True after 
os.fork()
+        self.register_code_callback = None
 
     @staticmethod
     def _mark_thread_disappeared(space):
diff --git a/pypy/interpreter/pycode.py b/pypy/interpreter/pycode.py
--- a/pypy/interpreter/pycode.py
+++ b/pypy/interpreter/pycode.py
@@ -14,9 +14,10 @@
     CO_OPTIMIZED, CO_NEWLOCALS, CO_VARARGS, CO_VARKEYWORDS, CO_NESTED,
     CO_GENERATOR, CO_KILL_DOCSTRING, CO_YIELD_INSIDE_TRY)
 from pypy.tool.stdlib_opcode import opcodedesc, HAVE_ARGUMENT
-from rpython.rlib.rarithmetic import intmask
+from rpython.rlib.rarithmetic import intmask, r_longlong
 from rpython.rlib.objectmodel import compute_hash
 from rpython.rlib import jit
+from rpython.rlib.debug import debug_start, debug_stop, debug_print
 
 
 class BytecodeCorruption(Exception):
@@ -56,6 +57,11 @@
     _immutable_fields_ = ["co_consts_w[*]", "co_names_w[*]", "co_varnames[*]",
                           "co_freevars[*]", "co_cellvars[*]", 
"_args_as_cellvars[*]"]
 
+    if sys.maxint == 2147483647:
+        _unique_id = 0 # XXX this is wrong, it won't work on 32bit
+    else:
+        _unique_id = 0x7000000000000000
+
     def __init__(self, space,  argcount, nlocals, stacksize, flags,
                      code, consts, names, varnames, filename,
                      name, firstlineno, lnotab, freevars, cellvars,
@@ -83,7 +89,7 @@
         self.magic = magic
         self._signature = cpython_code_signature(self)
         self._initialize()
-        self._vmprof_setup_maybe()
+        space.register_code_object(self)
 
     def _initialize(self):
         if self.co_cellvars:
@@ -125,9 +131,12 @@
             from pypy.objspace.std.mapdict import init_mapdict_cache
             init_mapdict_cache(self)
 
-    def _vmprof_setup_maybe(self):
-        # this is overridden only if _vmprof is enabled
-        pass
+        self._unique_id = PyCode._unique_id
+        PyCode._unique_id += 1
+
+    def _get_full_name(self):
+        return "py:%s:%d:%s" % (self.co_name, self.co_firstlineno,
+                                self.co_filename)
 
     def _cleanup_(self):
         if (self.magic == cpython_magic and
diff --git a/pypy/module/_vmprof/__init__.py b/pypy/module/_vmprof/__init__.py
--- a/pypy/module/_vmprof/__init__.py
+++ b/pypy/module/_vmprof/__init__.py
@@ -14,4 +14,5 @@
 
     def setup_after_space_initialization(self):
         # force the __extend__ hacks to occur early
-        import pypy.module._vmprof.interp_vmprof
+        from pypy.module._vmprof.interp_vmprof import VMProf
+        self.vmprof = VMProf()
diff --git a/pypy/module/_vmprof/interp_vmprof.py 
b/pypy/module/_vmprof/interp_vmprof.py
--- a/pypy/module/_vmprof/interp_vmprof.py
+++ b/pypy/module/_vmprof/interp_vmprof.py
@@ -1,17 +1,13 @@
-import py
+import py, os, struct
 from rpython.rtyper.lltypesystem import lltype, rffi, llmemory
 from rpython.translator.tool.cbuild import ExternalCompilationInfo
 from rpython.rtyper.annlowlevel import cast_instance_to_gcref, 
cast_base_ptr_to_instance
-from rpython.rlib.objectmodel import we_are_translated, CDefinedIntSymbolic
-from rpython.rlib import jit, rposix
-from rpython.tool.pairtype import extendabletype
+from rpython.rlib.objectmodel import we_are_translated
+from rpython.rlib import jit, rposix, entrypoint
 from pypy.interpreter.baseobjspace import W_Root
-from pypy.interpreter.error import oefmt, wrap_oserror
+from pypy.interpreter.error import oefmt, wrap_oserror, OperationError
 from pypy.interpreter.gateway import unwrap_spec
 from pypy.interpreter.pyframe import PyFrame
-from pypy.interpreter.pycode import PyCode
-
-FALSE_BUT_NON_CONSTANT = CDefinedIntSymbolic('0', default=0)
 
 ROOT = py.path.local(__file__).join('..')
 SRC = ROOT.join('src')
@@ -63,7 +59,7 @@
 pypy_vmprof_init = rffi.llexternal("pypy_vmprof_init", [], lltype.Void,
                                    compilation_info=eci)
 vmprof_enable = rffi.llexternal("vmprof_enable",
-                                [rffi.INT, rffi.INT, rffi.LONG],
+                                [rffi.INT, rffi.INT, rffi.LONG, rffi.INT],
                                 rffi.INT, compilation_info=eci)
 vmprof_disable = rffi.llexternal("vmprof_disable", [], rffi.INT,
                                  compilation_info=eci)
@@ -97,130 +93,85 @@
             return original_execute_frame(frame, w_inputvalue, operr)
 
 
-
-class __extend__(PyCode):
-    __metaclass__ = extendabletype
-
-    def _vmprof_setup_maybe(self):
-        self._vmprof_virtual_ip = _vmprof.get_next_virtual_IP()
-        self._vmprof_registered = 0
-
-# avoid rtyper warnings
-PyCode._vmprof_virtual_ip = 0
-PyCode._vmprof_registered = 0
-
-
-
[email protected]_lowlevel('main', [llmemory.GCREF],
+                                'pypy_vmprof_get_virtual_ip', True)
 def get_virtual_ip(gc_frame):
     frame = cast_base_ptr_to_instance(PyFrame, gc_frame)
     if jit._get_virtualizable_token(frame):
         return rffi.cast(rffi.VOIDP, 0)
     virtual_ip = do_get_virtual_ip(frame)
     return rffi.cast(rffi.VOIDP, virtual_ip)
-get_virtual_ip.c_name = 'pypy_vmprof_get_virtual_ip'
-get_virtual_ip._dont_inline_ = True
-
-def strncpy(src, tgt, tgt_ofs, count):
-    if len(src) < count:
-        count = len(src)
-    i = 0
-    while i < count:
-        tgt[i + tgt_ofs] = src[i]
-        i += 1
-    return i
-
-def int2str(num, s, ofs):
-    if num == 0:
-        s[ofs] = '0'
-        return 1
-    count = 0
-    c = num
-    while c != 0:
-        count += 1
-        c /= 10
-    pos = ofs + count - 1
-    c = num
-    while c != 0:
-        s[pos] = chr(ord('0') + c % 10)
-        c /= 10
-        pos -= 1
-    return count
 
 def do_get_virtual_ip(frame):
-    virtual_ip = frame.pycode._vmprof_virtual_ip
-    if not frame.pycode._vmprof_registered:
-        # we need to register this code object
-        name = frame.pycode.co_name
-        filename = frame.pycode.co_filename
-        firstlineno = frame.pycode.co_firstlineno
-        start = rffi.cast(rffi.VOIDP, virtual_ip)
-        end = start # ignored for now
-        #
-        # manually fill the C buffer; we cannot use str2charp because we
-        # cannot call malloc from a signal handler
-        strbuf = _vmprof.strbuf
-        ofs = strncpy("py:", _vmprof.strbuf, 0, len("py:"))
-        ofs += strncpy(filename, _vmprof.strbuf, ofs, 128)
-        _vmprof.strbuf[ofs] = ':'
-        ofs += 1
-        ofs += int2str(firstlineno, _vmprof.strbuf, ofs)
-        _vmprof.strbuf[ofs] = ':'
-        ofs += 1
-        ofs += strncpy(name, _vmprof.strbuf, ofs, 1024 - 1 - ofs)
-        _vmprof.strbuf[ofs] = '\x00'
-        vmprof_register_virtual_function(strbuf, start, end)
-        frame.pycode._vmprof_registered = 1
-    #
-    return virtual_ip
+    return frame.pycode._unique_id
 
 
 
 class VMProf(object):
     def __init__(self):
-        self.virtual_ip = 0x7000000000000000
         self.is_enabled = False
         self.ever_enabled = False
-        self.strbuf = lltype.malloc(rffi.CCHARP.TO, 1024, flavor='raw', 
immortal=True, zero=True)
+        self.mapping_so_far = [] # stored mapping in between runs
+        self.fileno = -1
 
-    def get_next_virtual_IP(self):
-        self.virtual_ip += 1
-        return self.virtual_ip
-
-    @jit.dont_look_inside
-    def _annotate_get_virtual_ip(self):
-        if FALSE_BUT_NON_CONSTANT:
-            # make sure it's annotated
-            gcref = rffi.cast(llmemory.GCREF, self.virtual_ip) # just a random 
non-constant value
-            get_virtual_ip(gcref)            
-
-    def enable(self, space, fileno, symno, period):
-        self._annotate_get_virtual_ip()
+    def enable(self, space, fileno, period):
         if self.is_enabled:
             raise oefmt(space.w_ValueError, "_vmprof already enabled")
+        self.fileno = fileno
         self.is_enabled = True
+        self.write_header(fileno, period)
         if not self.ever_enabled:
-            pypy_vmprof_init()
+            if we_are_translated():
+                pypy_vmprof_init()
             self.ever_enabled = True
-        res = vmprof_enable(fileno, symno, period)
+        for weakcode in space.all_code_objs.get_all_handles():
+            code = weakcode()
+            if code:
+                self.register_code(space, code)
+        space.set_code_callback(self.register_code)
+        if we_are_translated():
+            # does not work untranslated
+            res = vmprof_enable(fileno, -1, period, 0)
+        else:
+            res = 0
         if res == -1:
             raise wrap_oserror(space, OSError(rposix.get_errno(),
                                               "_vmprof.enable"))
 
+    def write_header(self, fileno, period):
+        if period == -1:
+            period_usec = 1000000 / 100 #  100hz
+        else:
+            period_usec = period
+        os.write(fileno, struct.pack("lllll", 0, 3, 0, period_usec, 0))
+
+    def register_code(self, space, code):
+        if self.fileno == -1:
+            raise OperationError(space.w_RuntimeError,
+                                 space.wrap("vmprof not running"))
+        name = code._get_full_name()
+        s = '\x02' + struct.pack("ll", code._unique_id, len(name)) + name
+        os.write(self.fileno, s)
+
     def disable(self, space):
         if not self.is_enabled:
             raise oefmt(space.w_ValueError, "_vmprof not enabled")
         self.is_enabled = False
-        res = vmprof_disable()
+        self.fileno = -1
+        if we_are_translated():
+           # does not work untranslated
+            res = vmprof_disable()
+        else:
+            res = 0
+        space.set_code_callback(None)
         if res == -1:
             raise wrap_oserror(space, OSError(rposix.get_errno(),
                                               "_vmprof.disable"))
 
-_vmprof = VMProf()
-
-@unwrap_spec(fileno=int, symno=int, period=int)
-def enable(space, fileno, symno, period=-1):
-    _vmprof.enable(space, fileno, symno, period)
+@unwrap_spec(fileno=int, period=int)
+def enable(space, fileno, period=-1):
+    space.getbuiltinmodule('_vmprof').vmprof.enable(space, fileno, period)
 
 def disable(space):
-    _vmprof.disable(space)
+    space.getbuiltinmodule('_vmprof').vmprof.disable(space)
 
diff --git a/pypy/module/_vmprof/src/vmprof.c b/pypy/module/_vmprof/src/vmprof.c
--- a/pypy/module/_vmprof/src/vmprof.c
+++ b/pypy/module/_vmprof/src/vmprof.c
@@ -35,7 +35,7 @@
 #define MAX_STACK_DEPTH 64
 
 static FILE* profile_file;
-static FILE* symbol_file;
+static FILE* symbol_file = NULL;
 void* vmprof_mainloop_func;
 static ptrdiff_t mainloop_sp_offset;
 static vmprof_get_virtual_ip_t mainloop_get_virtual_ip;
@@ -45,10 +45,19 @@
  * functions to write a profile file compatible with gperftools
  * *************************************************************
  */
+
+#define MARKER_STACKTRACE '\x01'
+#define MARKER_VIRTUAL_IP '\x02'
+#define MARKER_TRAILER '\x03'
+
 static void prof_word(FILE* f, long x) {
     fwrite(&x, sizeof(x), 1, f);
 }
 
+static void prof_char(FILE *f, char x) {
+       fwrite(&x, sizeof(x), 1, f);
+}
+
 static void prof_header(FILE* f, long period_usec) {
     prof_word(f, 0);
     prof_word(f, 3);
@@ -59,18 +68,13 @@
 
 static void prof_write_stacktrace(FILE* f, void** stack, int depth, int count) 
{
     int i;
+       prof_char(f, MARKER_STACKTRACE);
     prof_word(f, count);
     prof_word(f, depth);
     for(i=0; i<depth; i++)
         prof_word(f, (long)stack[i]);
 }
 
-static void prof_binary_trailer(FILE* f) {
-    prof_word(f, 0);
-    prof_word(f, 1);
-    prof_word(f, 0);
-}
-
 
 /* ******************************************************
  * libunwind workaround for process JIT frames correctly
@@ -198,21 +202,26 @@
  * *************************************************************
  */
 
-static int open_profile(int fd, int sym_fd, long period_usec) {
+static int open_profile(int fd, int sym_fd, long period_usec, int 
write_header) {
        if ((fd = dup(fd)) == -1) {
                return -1;
        }
-       if ((sym_fd = dup(sym_fd)) == -1) {
-               return -1;
-       }       
+       if (sym_fd != -1) {
+               if ((sym_fd = dup(sym_fd)) == -1) {
+                       return -1;
+               }
+       }
     profile_file = fdopen(fd, "wb");
        if (!profile_file) {
                return -1;
        }
-    prof_header(profile_file, period_usec);
-    symbol_file = fdopen(sym_fd, "w");
-       if (!symbol_file) {
-               return -1;
+       if (write_header)
+               prof_header(profile_file, period_usec);
+       if (sym_fd != -1) {
+               symbol_file = fdopen(sym_fd, "w");
+               if (!symbol_file) {
+                       return -1;
+               }
        }
        return 0;
 }
@@ -222,7 +231,7 @@
     FILE* src;
     char buf[BUFSIZ];
     size_t size;
-    prof_binary_trailer(profile_file);
+       prof_char(profile_file, MARKER_TRAILER);
 
     // copy /proc/PID/maps to the end of the profile file
     sprintf(buf, "/proc/%d/maps", getpid());
@@ -232,7 +241,10 @@
     }
     fclose(src);
     fclose(profile_file);
-    fclose(symbol_file);
+       if (symbol_file) {
+               fclose(symbol_file);
+               symbol_file = NULL;
+       }
        return 0;
 }
 
@@ -291,10 +303,10 @@
     mainloop_get_virtual_ip = get_virtual_ip;
 }
 
-int vmprof_enable(int fd, int sym_fd, long period_usec) {
+int vmprof_enable(int fd, int sym_fd, long period_usec, int write_header) {
     if (period_usec == -1)
         period_usec = 1000000 / 100; /* 100hz */
-    if (open_profile(fd, sym_fd, period_usec) == -1) {
+    if (open_profile(fd, sym_fd, period_usec, write_header) == -1) {
                return -1;
        }
     if (install_sigprof_handler() == -1) {
diff --git a/pypy/module/_vmprof/src/vmprof.h b/pypy/module/_vmprof/src/vmprof.h
--- a/pypy/module/_vmprof/src/vmprof.h
+++ b/pypy/module/_vmprof/src/vmprof.h
@@ -12,7 +12,7 @@
 void vmprof_register_virtual_function(const char* name, void* start, void* 
end);
 
 
-int vmprof_enable(int fd, int sym_fd, long period_usec);
+int vmprof_enable(int fd, int sym_fd, long period_usec, int write_header);
 int vmprof_disable(void);
 
 #endif
diff --git a/pypy/module/_vmprof/test/test__vmprof.py 
b/pypy/module/_vmprof/test/test__vmprof.py
--- a/pypy/module/_vmprof/test/test__vmprof.py
+++ b/pypy/module/_vmprof/test/test__vmprof.py
@@ -1,55 +1,55 @@
-from rpython.rtyper.lltypesystem import rffi, lltype
-from pypy.module._vmprof import interp_vmprof
-from pypy.module._vmprof.interp_vmprof import do_get_virtual_ip, _vmprof
 
-class FakePyFrame(object):
+import tempfile
+from pypy.tool.pytest.objspace import gettestobjspace
 
-    def __init__(self, pycode):
-        self.pycode = pycode
+class AppTestVMProf(object):
+    def setup_class(cls):
+        cls.space = gettestobjspace(usemodules=['_vmprof', 'struct'])
+        cls.tmpfile = tempfile.NamedTemporaryFile()
+        cls.w_tmpfileno = cls.space.wrap(cls.tmpfile.fileno())
+        cls.w_tmpfilename = cls.space.wrap(cls.tmpfile.name)
+        cls.tmpfile2 = tempfile.NamedTemporaryFile()
+        cls.w_tmpfileno2 = cls.space.wrap(cls.tmpfile2.fileno())
+        cls.w_tmpfilename2 = cls.space.wrap(cls.tmpfile2.name)
 
-class FakePyCode(object):
+    def test_import_vmprof(self):
+        import struct, sys
 
-    _vmprof_setup_maybe = interp_vmprof.PyCode._vmprof_setup_maybe.im_func
+        WORD = struct.calcsize('l')
+        
+        def count(s):
+            i = 0
+            count = 0
+            i += 5 * WORD # header
+            while i < len(s):
+                assert s[i] == '\x02'
+                i += 1
+                _, size = struct.unpack("ll", s[i:i + 2 * WORD])
+                count += 1
+                i += 2 * WORD + size
+            return count
+        
+        import _vmprof
+        _vmprof.enable(self.tmpfileno)
+        _vmprof.disable()
+        s = open(self.tmpfilename).read()
+        no_of_codes = count(s)
+        assert no_of_codes > 10
+        d = {}
 
-    def __init__(self, co_name):
-        self.co_name = co_name
-        self.co_filename = 'filename'
-        self.co_firstlineno = 13
-        self._vmprof_setup_maybe()
+        exec """def foo():
+            pass
+        """ in d
 
-def test_get_virtual_ip(monkeypatch):
-    functions = []
-    def register_virtual_function(name, start, end):
-        name = rffi.charp2str(name)
-        start = rffi.cast(lltype.Signed, start)
-        end = rffi.cast(lltype.Signed, end)
-        functions.append((name, start, end))
-    monkeypatch.setattr(interp_vmprof, 'vmprof_register_virtual_function', 
register_virtual_function)
-    #
-    mycode = FakePyCode('foo')
-    assert mycode._vmprof_virtual_ip < 0
-    myframe = FakePyFrame(mycode)
+        _vmprof.enable(self.tmpfileno2)
 
-    _vmprof.counter = 42
-    ip = do_get_virtual_ip(myframe)
-    assert ip == mycode._vmprof_virtual_ip
-    assert functions == [('py:filename:13:foo', ip, ip)]
+        exec """def foo2():
+            pass
+        """ in d
 
-    # the second time, we don't register it again
-    functions = []
-    ip = do_get_virtual_ip(myframe)
-    assert ip == mycode._vmprof_virtual_ip
-    assert functions == []
-
-    # now, let's try with a long name
-    mycode = FakePyCode('abcde' + 'f' * 20000)
-    myframe = FakePyFrame(mycode)
-    functions = []
-    ip2 = do_get_virtual_ip(myframe)
-    assert ip2 == mycode._vmprof_virtual_ip
-    assert ip2 < ip # because it was generated later
-    assert len(functions) == 1
-    name, start, end = functions[0]
-    assert len(name) < 1025
-    assert name == 'py:filename:13:abcde' + 'f' * (1024 - 20 - 1)
-    
+        _vmprof.disable()
+        s = open(self.tmpfilename2).read()
+        no_of_codes2 = count(s)
+        assert "py:foo:" in s
+        assert "py:foo2:" in s
+        assert no_of_codes2 >= no_of_codes + 2 # some extra codes from tests
diff --git a/rpython/jit/backend/llsupport/assembler.py 
b/rpython/jit/backend/llsupport/assembler.py
--- a/rpython/jit/backend/llsupport/assembler.py
+++ b/rpython/jit/backend/llsupport/assembler.py
@@ -278,6 +278,9 @@
         # YYY very minor leak -- we need the counters to stay alive
         # forever, just because we want to report them at the end
         # of the process
+
+        # XXX the numbers here are ALMOST unique, but not quite, use a counter
+        #     or something
         struct = lltype.malloc(DEBUG_COUNTER, flavor='raw',
                                track_allocation=False)
         struct.i = 0
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to