[pypy-commit] pypy vmprof: (arigo, antocuni): implement the _vmprof module, based on vmprof.c; it patches PyFrame.execute_frame so that it's called through a trampoline, which is then recognized by vmprof's signal handler
antocuni
noreply at buildbot.pypy.org
Fri Oct 24 18:15:15 CEST 2014
Author: Antonio Cuni <anto.cuni at gmail.com>
Branch: vmprof
Changeset: r74183:73fc0dc6f6e1
Date: 2014-10-24 17:09 +0100
http://bitbucket.org/pypy/pypy/changeset/73fc0dc6f6e1/
Log: (arigo, antocuni): implement the _vmprof module, based on vmprof.c;
it patches PyFrame.execute_frame so that it's called through a
trampoline, which is then recognized by vmprof's signal handler
diff --git a/pypy/interpreter/pycode.py b/pypy/interpreter/pycode.py
--- a/pypy/interpreter/pycode.py
+++ b/pypy/interpreter/pycode.py
@@ -83,6 +83,7 @@
self.magic = magic
self._signature = cpython_code_signature(self)
self._initialize()
+ self._vmprof_setup_maybe()
def _initialize(self):
if self.co_cellvars:
@@ -124,6 +125,10 @@
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
+
def _cleanup_(self):
if (self.magic == cpython_magic and
'__pypy__' not in sys.builtin_module_names):
diff --git a/pypy/module/_vmprof/__init__.py b/pypy/module/_vmprof/__init__.py
new file mode 100644
--- /dev/null
+++ b/pypy/module/_vmprof/__init__.py
@@ -0,0 +1,14 @@
+from pypy.interpreter.mixedmodule import MixedModule
+
+class Module(MixedModule):
+ """
+ Write me :)
+ """
+
+ appleveldefs = {
+ }
+
+ interpleveldefs = {
+ 'enable': 'interp_vmprof.enable',
+ 'disable': 'interp_vmprof.disable',
+ }
diff --git a/pypy/module/_vmprof/interp_vmprof.py b/pypy/module/_vmprof/interp_vmprof.py
new file mode 100644
--- /dev/null
+++ b/pypy/module/_vmprof/interp_vmprof.py
@@ -0,0 +1,149 @@
+import py
+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.tool.pairtype import extendabletype
+from pypy.interpreter.baseobjspace import W_Root
+from pypy.interpreter.error import OperationError, oefmt
+from pypy.interpreter.gateway import interp2app, 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')
+
+eci = ExternalCompilationInfo(
+ includes=['vmprof.h', 'trampoline.h'],
+ include_dirs = [SRC],
+ separate_module_files = [SRC.join('vmprof.c'), SRC.join('trampoline.s')],
+ libraries = ['unwind'],
+ post_include_bits=["""
+ void* pypy_vmprof_get_virtual_ip(void*);
+ void pypy_vmprof_init(void);
+ """],
+ separate_module_sources=["""
+ void pypy_vmprof_init(void) {
+ vmprof_set_mainloop(pypy_execute_frame_trampoline, 0, pypy_vmprof_get_virtual_ip);
+ }
+ """]
+)
+
+pypy_execute_frame_trampoline = rffi.llexternal(
+ "pypy_execute_frame_trampoline",
+ [llmemory.GCREF, llmemory.GCREF, llmemory.GCREF],
+ llmemory.GCREF,
+ compilation_info=eci,
+ _nowrapper=True, sandboxsafe=True)
+
+pypy_vmprof_init = rffi.llexternal("pypy_vmprof_init", [], lltype.Void, compilation_info=eci)
+vmprof_enable = rffi.llexternal("vmprof_enable", [rffi.CCHARP, rffi.LONG], lltype.Void, compilation_info=eci)
+vmprof_disable = rffi.llexternal("vmprof_disable", [], lltype.Void, compilation_info=eci)
+
+vmprof_register_virtual_function = rffi.llexternal("vmprof_register_virtual_function",
+ [rffi.CCHARP, rffi.VOIDP, rffi.VOIDP],
+ lltype.Void,
+ compilation_info=eci)
+
+original_execute_frame = PyFrame.execute_frame.im_func
+original_execute_frame.c_name = 'pypy_pyframe_execute_frame'
+
+class __extend__(PyFrame):
+ def execute_frame(frame, w_inputvalue=None, operr=None):
+ if we_are_translated() and not FALSE_BUT_NON_CONSTANT:
+ # if we are translated, call the trampoline
+ gc_frame = cast_instance_to_gcref(frame)
+ gc_inputvalue = cast_instance_to_gcref(w_inputvalue)
+ gc_operr = cast_instance_to_gcref(operr)
+ gc_result = pypy_execute_frame_trampoline(gc_frame, gc_inputvalue, gc_operr)
+ return cast_base_ptr_to_instance(W_Root, gc_result)
+ else:
+ # else, just call the original function. The FALSE_BUT_NON_CONSTANT is
+ # needed to convince the annotator to always see
+ # original_execute_frame
+ 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
+
+
+def get_virtual_ip(gc_frame):
+ frame = cast_base_ptr_to_instance(PyFrame, gc_frame)
+ virtual_ip = do_get_virtual_ip(frame)
+ return rffi.cast(rffi.VOIDP, virtual_ip)
+
+def do_get_virtual_ip(frame):
+ virtual_ip = frame.pycode._vmprof_virtual_ip
+ if frame.pycode._vmprof_registered != _vmprof.counter:
+ # we need to register this code object
+ name = frame.pycode.co_name
+ 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
+ strbuf[0] = 'p'
+ strbuf[1] = 'y'
+ strbuf[2] = ':'
+ maxbuflen = min(len(name), 124)
+ i = 0
+ while i < maxbuflen:
+ strbuf[i+3] = name[i]
+ i += 1
+ strbuf[i+3] = '\0'
+ #
+ vmprof_register_virtual_function(strbuf, start, end)
+ frame.pycode._vmprof_registered = _vmprof.counter
+ #
+ return virtual_ip
+get_virtual_ip.c_name = 'pypy_vmprof_get_virtual_ip'
+
+
+class VMProf(object):
+ def __init__(self):
+ self.virtual_ip = 0
+ self.counter = 0 # the number of times we called enable()
+ self.is_enabled = False
+ self.strbuf = lltype.malloc(rffi.CCHARP.TO, 128, flavor='raw', immortal=True, zero=True)
+
+ def get_next_virtual_IP(self):
+ self.virtual_ip -= 1
+ return self.virtual_ip
+
+ def _annotate_get_virtual_ip(self):
+ if FALSE_BUT_NON_CONSTANT:
+ # make sure it's annotated
+ get_virtual_ip(lltype.nullptr(llmemory.GCREF.TO))
+
+ def enable(self, filename, period):
+ self._annotate_get_virtual_ip()
+ if self.is_enabled:
+ # XXX raise an app-level exception
+ print 'IGNORING _vmprof.enable()'
+ return
+ self.is_enabled = True
+ pypy_vmprof_init()
+ self.counter += 1
+ vmprof_enable(filename, period)
+
+ def disable(self):
+ vmprof_disable()
+ self.is_enabled = False
+
+_vmprof = VMProf()
+
+ at unwrap_spec(filename=str, period=int)
+def enable(space, filename, period=-1):
+ _vmprof.enable(filename, period)
+
+def disable(space):
+ _vmprof.disable()
diff --git a/pypy/module/_vmprof/src/trampoline.h b/pypy/module/_vmprof/src/trampoline.h
new file mode 100644
--- /dev/null
+++ b/pypy/module/_vmprof/src/trampoline.h
@@ -0,0 +1,1 @@
+void* pypy_execute_frame_trampoline(void*, void*, void*);
diff --git a/pypy/module/_vmprof/src/trampoline.s b/pypy/module/_vmprof/src/trampoline.s
new file mode 100644
--- /dev/null
+++ b/pypy/module/_vmprof/src/trampoline.s
@@ -0,0 +1,15 @@
+ .text
+ .p2align 4,,-1
+ .globl pypy_execute_frame_trampoline
+ .type pypy_execute_frame_trampoline, @function
+pypy_execute_frame_trampoline:
+ .cfi_startproc
+ pushq %rdi
+ .cfi_def_cfa_offset 16
+ movabs pypy_pyframe_execute_frame, %rax
+ callq *%rax
+ popq %rdi
+ .cfi_def_cfa_offset 8
+ ret
+ .cfi_endproc
+ .size pypy_execute_frame_trampoline, .-pypy_execute_frame_trampoline
diff --git a/pypy/module/_vmprof/test/__init__.py b/pypy/module/_vmprof/test/__init__.py
new file mode 100644
diff --git a/pypy/module/_vmprof/test/test__vmprof.py b/pypy/module/_vmprof/test/test__vmprof.py
new file mode 100644
--- /dev/null
+++ b/pypy/module/_vmprof/test/test__vmprof.py
@@ -0,0 +1,53 @@
+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):
+
+ def __init__(self, pycode):
+ self.pycode = pycode
+
+class FakePyCode(object):
+
+ _vmprof_setup_maybe = interp_vmprof.PyCode._vmprof_setup_maybe.im_func
+
+ def __init__(self, co_name):
+ self.co_name = co_name
+ self._vmprof_setup_maybe()
+
+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.counter = 42
+ ip = do_get_virtual_ip(myframe)
+ assert ip == mycode._vmprof_virtual_ip
+ assert functions == [('py:foo', ip, ip)]
+
+ # 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' * 200)
+ 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) == 127
+ assert name == 'py:' + ('abcde'*200)[:124]
+
More information about the pypy-commit
mailing list