Author: Armin Rigo <[email protected]>
Branch:
Changeset: r93472:13a87780bd5a
Date: 2017-12-18 13:43 +0100
http://bitbucket.org/pypy/pypy/changeset/13a87780bd5a/
Log: merge heads
diff --git a/extra_tests/requirements.txt b/extra_tests/requirements.txt
--- a/extra_tests/requirements.txt
+++ b/extra_tests/requirements.txt
@@ -1,2 +1,3 @@
pytest
hypothesis
+vmprof
diff --git a/extra_tests/test_vmprof_greenlet.py
b/extra_tests/test_vmprof_greenlet.py
new file mode 100644
--- /dev/null
+++ b/extra_tests/test_vmprof_greenlet.py
@@ -0,0 +1,28 @@
+import time
+import pytest
+import greenlet
+import vmprof
+
+def count_samples(filename):
+ stats = vmprof.read_profile(filename)
+ return len(stats.profiles)
+
+def cpuburn(duration):
+ end = time.time() + duration
+ while time.time() < end:
+ pass
+
+def test_sampling_inside_callback(tmpdir):
+ # see also test_sampling_inside_callback inside
+ # pypy/module/_continuation/test/test_stacklet.py
+ #
+ G = greenlet.greenlet(cpuburn)
+ fname = tmpdir.join('log.vmprof')
+ with fname.open('w+b') as f:
+ vmprof.enable(f.fileno(), 1/250.0)
+ G.switch(0.1)
+ vmprof.disable()
+
+ samples = count_samples(str(fname))
+ # 0.1 seconds at 250Hz should be 25 samples
+ assert 23 < samples < 27
diff --git a/pypy/doc/whatsnew-head.rst b/pypy/doc/whatsnew-head.rst
--- a/pypy/doc/whatsnew-head.rst
+++ b/pypy/doc/whatsnew-head.rst
@@ -31,7 +31,7 @@
Upgrade the _vmprof backend to vmprof 0.4.10
.. branch: fix-vmprof-stacklet-switch
-
+.. branch: fix-vmprof-stacklet-switch-2
Fix a vmprof+continulets (i.e. greenelts, eventlet, gevent, ...)
.. branch: win32-vcvars
@@ -39,3 +39,4 @@
.. branch: rdict-fast-hash
Make it possible to declare that the hash function of an r_dict is fast in
RPython.
+
diff --git a/pypy/module/_continuation/interp_continuation.py
b/pypy/module/_continuation/interp_continuation.py
--- a/pypy/module/_continuation/interp_continuation.py
+++ b/pypy/module/_continuation/interp_continuation.py
@@ -1,5 +1,6 @@
from rpython.rlib.rstacklet import StackletThread
from rpython.rlib import jit
+from rpython.rlib import rvmprof
from pypy.interpreter.error import OperationError, get_cleared_operation_error
from pypy.interpreter.executioncontext import ExecutionContext
from pypy.interpreter.baseobjspace import W_Root
@@ -222,12 +223,15 @@
self.h = h
global_state.clear()
try:
+ rvmprof.start_sampling()
frame = self.bottomframe
w_result = frame.execute_frame()
except Exception as e:
global_state.propagate_exception = e
else:
global_state.w_value = w_result
+ finally:
+ rvmprof.stop_sampling()
self.sthread.ec.topframeref = jit.vref_None
global_state.origin = self
global_state.destination = self
diff --git a/pypy/module/_continuation/test/test_stacklet.py
b/pypy/module/_continuation/test/test_stacklet.py
--- a/pypy/module/_continuation/test/test_stacklet.py
+++ b/pypy/module/_continuation/test/test_stacklet.py
@@ -1,7 +1,10 @@
+import pytest
import os
+from rpython.rlib.rvmprof.test.support import fakevmprof
+from pypy.interpreter.gateway import interp2app
from pypy.module._continuation.test.support import BaseAppTest
-
[email protected]('app_fakevmprof')
class AppTestStacklet(BaseAppTest):
def setup_class(cls):
BaseAppTest.setup_class.im_func(cls)
@@ -34,10 +37,34 @@
return res
return stack
""")
+ cls.w_appdirect = cls.space.wrap(cls.runappdirect)
if cls.runappdirect:
# make sure that "self.stack" does not pass the self
cls.w_stack = staticmethod(cls.w_stack.im_func)
+
+ @pytest.fixture
+ def app_fakevmprof(self, fakevmprof):
+ """
+ This is automaticaly re-initialized for every method: thanks to
+ fakevmprof's finalizer, it checks that we called {start,stop}_sampling
+ the in pairs
+ """
+ w = self.space.wrap
+ i2a = interp2app
+ def is_sampling_enabled(space):
+ return space.wrap(fakevmprof.is_sampling_enabled)
+ self.w_is_sampling_enabled = w(i2a(is_sampling_enabled))
+ #
+ def start_sampling(space):
+ fakevmprof.start_sampling()
+ self.w_start_sampling = w(i2a(start_sampling))
+ #
+ def stop_sampling(space):
+ fakevmprof.stop_sampling()
+ self.w_stop_sampling = w(i2a(stop_sampling))
+
+
def test_new_empty(self):
from _continuation import continulet
#
@@ -770,3 +797,25 @@
continulet.switch(c1, to=c2)
raises(error, continulet.switch, c1, to=c2)
+
+ def test_sampling_inside_callback(self):
+ if self.appdirect:
+ # see also
+ # extra_tests.test_vmprof_greenlet.test_sampling_inside_callback
+ # for a "translated" version of this test
+ skip("we can't run this until we have _vmprof.is_sampling_enabled")
+ from _continuation import continulet
+ #
+ def my_callback(c1):
+ assert self.is_sampling_enabled()
+ return 42
+ #
+ try:
+ self.start_sampling()
+ assert self.is_sampling_enabled()
+ c = continulet(my_callback)
+ res = c.switch()
+ assert res == 42
+ assert self.is_sampling_enabled()
+ finally:
+ self.stop_sampling()
diff --git a/pypy/module/_continuation/test/test_translated.py
b/pypy/module/_continuation/test/test_translated.py
--- a/pypy/module/_continuation/test/test_translated.py
+++ b/pypy/module/_continuation/test/test_translated.py
@@ -1,4 +1,5 @@
import py
+import pytest
try:
import _continuation
except ImportError:
@@ -101,11 +102,7 @@
particular, we need to ensure that vmprof does not sample the stack in
the middle of a switch, else we read nonsense.
"""
- try:
- import _vmprof
- except ImportError:
- py.test.skip("no _vmprof")
- #
+ _vmprof = pytest.importorskip('_vmprof')
def switch_forever(c):
while True:
c.switch()
diff --git a/rpython/rlib/rstacklet.py b/rpython/rlib/rstacklet.py
--- a/rpython/rlib/rstacklet.py
+++ b/rpython/rlib/rstacklet.py
@@ -3,7 +3,7 @@
from rpython.rlib import jit
from rpython.rlib.objectmodel import fetch_translated_config
from rpython.rtyper.lltypesystem import lltype, llmemory
-from rpython.rlib.rvmprof import cintf
+from rpython.rlib import rvmprof
DEBUG = False
@@ -25,12 +25,12 @@
def new(self, callback, arg=llmemory.NULL):
if DEBUG:
callback = _debug_wrapper(callback)
- x = cintf.save_rvmprof_stack()
+ x = rvmprof.save_stack()
try:
- cintf.empty_rvmprof_stack()
+ rvmprof.empty_stack()
h = self._gcrootfinder.new(self, callback, arg)
finally:
- cintf.restore_rvmprof_stack(x)
+ rvmprof.restore_stack(x)
if DEBUG:
debug.add(h)
return h
@@ -40,11 +40,11 @@
def switch(self, stacklet):
if DEBUG:
debug.remove(stacklet)
- x = cintf.save_rvmprof_stack()
+ x = rvmprof.save_stack()
try:
h = self._gcrootfinder.switch(stacklet)
finally:
- cintf.restore_rvmprof_stack(x)
+ rvmprof.restore_stack(x)
if DEBUG:
debug.add(h)
return h
diff --git a/rpython/rlib/rvmprof/__init__.py b/rpython/rlib/rvmprof/__init__.py
--- a/rpython/rlib/rvmprof/__init__.py
+++ b/rpython/rlib/rvmprof/__init__.py
@@ -56,10 +56,27 @@
return None
def stop_sampling():
- from rpython.rlib.rvmprof.cintf import vmprof_stop_sampling
- fd = vmprof_stop_sampling()
- return rffi.cast(lltype.Signed, fd)
+ return _get_vmprof().stop_sampling()
def start_sampling():
- from rpython.rlib.rvmprof.cintf import vmprof_start_sampling
- vmprof_start_sampling()
+ return _get_vmprof().start_sampling()
+
+# ----------------
+# stacklet support
+# ----------------
+#
+# Ideally, vmprof_tl_stack, VMPROFSTACK etc. should be part of "self.cintf":
+# not sure why they are a global. Eventually, we should probably fix all this
+# mess.
+from rpython.rlib.rvmprof.cintf import vmprof_tl_stack, VMPROFSTACK
+
+def save_stack():
+ stop_sampling()
+ return vmprof_tl_stack.get_or_make_raw()
+
+def empty_stack():
+ vmprof_tl_stack.setraw(lltype.nullptr(VMPROFSTACK))
+
+def restore_stack(x):
+ vmprof_tl_stack.setraw(x)
+ start_sampling()
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
@@ -122,32 +122,16 @@
lltype.Signed,
compilation_info=eci,
_nowrapper=True)
+ vmprof_stop_sampling = rffi.llexternal("vmprof_stop_sampling", [],
+ rffi.INT, compilation_info=eci,
+ _nowrapper=True)
+ vmprof_start_sampling = rffi.llexternal("vmprof_start_sampling", [],
+ lltype.Void, compilation_info=eci,
+ _nowrapper=True)
+
return CInterface(locals())
-# this is always present, but compiles to no-op if RPYTHON_VMPROF is not
-# defined (i.e. if we don't actually use vmprof in the generated C)
-auto_eci = ExternalCompilationInfo(post_include_bits=["""
-#ifndef RPYTHON_VMPROF
-# define vmprof_stop_sampling() (-1)
-# define vmprof_start_sampling() ((void)0)
-#endif
-"""])
-
-if get_translation_config() is None:
- # tests need the full eci here
- _eci = global_eci
-else:
- _eci = auto_eci
-
-vmprof_stop_sampling = rffi.llexternal("vmprof_stop_sampling", [],
- rffi.INT, compilation_info=_eci,
- _nowrapper=True)
-vmprof_start_sampling = rffi.llexternal("vmprof_start_sampling", [],
- lltype.Void, compilation_info=_eci,
- _nowrapper=True)
-
-
class CInterface(object):
def __init__(self, namespace):
for k, v in namespace.iteritems():
@@ -232,20 +216,6 @@
leave_code(s)
#
-# stacklet support
-
-def save_rvmprof_stack():
- vmprof_stop_sampling()
- return vmprof_tl_stack.get_or_make_raw()
-
-def empty_rvmprof_stack():
- vmprof_tl_stack.setraw(lltype.nullptr(VMPROFSTACK))
-
-def restore_rvmprof_stack(x):
- vmprof_tl_stack.setraw(x)
- vmprof_start_sampling()
-
-#
# traceback support
def get_rvmprof_stack():
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
@@ -168,6 +168,21 @@
if self.cintf.vmprof_register_virtual_function(name, uid, 500000) < 0:
raise VMProfError("vmprof buffers full! disk full or too slow")
+ def stop_sampling(self):
+ """
+ Temporarily stop the sampling of stack frames. Signals are still
+ delivered, but are ignored.
+ """
+ fd = self.cintf.vmprof_stop_sampling()
+ return rffi.cast(lltype.Signed, fd)
+
+ def start_sampling(self):
+ """
+ Undo the effect of stop_sampling
+ """
+ self.cintf.vmprof_start_sampling()
+
+
def vmprof_execute_code(name, get_code_fn, result_class=None,
_hack_update_stack_untranslated=False):
"""Decorator to be used on the function that interprets a code object.
diff --git a/rpython/rlib/rvmprof/test/support.py
b/rpython/rlib/rvmprof/test/support.py
new file mode 100644
--- /dev/null
+++ b/rpython/rlib/rvmprof/test/support.py
@@ -0,0 +1,45 @@
+import pytest
+from rpython.rlib import rvmprof
+
+class FakeVMProf(object):
+
+ def __init__(self):
+ self._enabled = False
+ self._ignore_signals = 1
+
+ # --- VMProf official API ---
+ # add fake methods as needed by the tests
+
+ def stop_sampling(self):
+ self._ignore_signals += 1
+
+ def start_sampling(self):
+ assert self._ignore_signals > 0, ('calling start_sampling() without '
+ 'the corresponding stop_sampling()?')
+ self._ignore_signals -= 1
+
+ # --- FakeVMProf specific API ---
+ # this API is not part of rvmprof, but available only inside tests using
+ # fakevmprof
+
+ @property
+ def is_sampling_enabled(self):
+ return self._ignore_signals == 0
+
+ def check_status(self):
+ """
+ To be called during test teardown
+ """
+ if self._ignore_signals != 1:
+ msg = ('Invalid value for fakevmprof._ignore_signals: expected 1, '
+ 'got %d. This probably means that you called '
+ '{start,stop}_sampling() a wrong number of times')
+ raise ValueError, msg % self._ignore_signals
+
+
[email protected]
+def fakevmprof(request, monkeypatch):
+ fake = FakeVMProf()
+ monkeypatch.setattr(rvmprof.rvmprof, '_vmprof_instance', fake)
+ request.addfinalizer(fake.check_status)
+ return fake
diff --git a/rpython/rlib/rvmprof/test/test_support.py
b/rpython/rlib/rvmprof/test/test_support.py
new file mode 100644
--- /dev/null
+++ b/rpython/rlib/rvmprof/test/test_support.py
@@ -0,0 +1,42 @@
+import pytest
+from rpython.rlib import rvmprof
+from rpython.rlib.rvmprof.test.support import FakeVMProf, fakevmprof
+
+class TestFakeVMProf(object):
+
+ def test_sampling(self):
+ fake = FakeVMProf()
+ assert not fake.is_sampling_enabled
+ #
+ fake.start_sampling()
+ assert fake.is_sampling_enabled
+ #
+ fake.stop_sampling()
+ fake.stop_sampling()
+ assert not fake.is_sampling_enabled
+ #
+ fake.start_sampling()
+ assert not fake.is_sampling_enabled
+ fake.start_sampling()
+ assert fake.is_sampling_enabled
+ #
+ pytest.raises(AssertionError, "fake.start_sampling()")
+
+ def test_check_status(self):
+ fake = FakeVMProf()
+ fake.stop_sampling()
+ pytest.raises(ValueError, "fake.check_status()")
+
+
+class TestFixture(object):
+
+ def test_fixture(self, fakevmprof):
+ assert isinstance(fakevmprof, FakeVMProf)
+ assert rvmprof._get_vmprof() is fakevmprof
+ #
+ # tweak sampling using the "real" API, and check that we actually used
+ # the fake
+ rvmprof.start_sampling()
+ assert fakevmprof.is_sampling_enabled
+ rvmprof.stop_sampling()
+ assert not fakevmprof.is_sampling_enabled
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit