Change in vdsm[master]: profile: Introduce profile decorator

nsoffer at redhat.com nsoffer at redhat.com
Mon May 5 15:32:21 UTC 2014


Nir Soffer has uploaded a new change for review.

Change subject: profile: Introduce profile decorator
......................................................................

profile: Introduce profile decorator

The profile decorator can be used for profiling certain code path, where
whole application profile is not needed.

To profile a function, decorate it:

    from profile import profile
    import slowmodule
    ...
    @profile('expensive_stuff.prof')
    def test_expensive_stuff():
        slowmodule.do_expensive_stuff()

The recorded profile will include all threads running or started while
my_function was executed.

Note that you cannot use application wide profile and function profile
in the same time. To prevent confusion, a profile.Error is raised if you
try to start a profile while the profiler is already running.

Change-Id: I2bc861832bbbda67937b5663f6cfbc1a54bfbf7d
Signed-off-by: Nir Soffer <nsoffer at redhat.com>
---
M lib/vdsm/profile.py
M tests/profileTests.py
2 files changed, 86 insertions(+), 11 deletions(-)


  git pull ssh://gerrit.ovirt.org:29418/vdsm refs/changes/89/27389/1

diff --git a/lib/vdsm/profile.py b/lib/vdsm/profile.py
index 9c50c89..746ce1d 100644
--- a/lib/vdsm/profile.py
+++ b/lib/vdsm/profile.py
@@ -22,33 +22,34 @@
 This module provides cpu profiling.
 """
 
+from functools import wraps
 import os
 import logging
 
 from vdsm import constants
 from vdsm.config import config
 
-# Import yappi only if profile is enabled
+# Import yappi lazily when profile is started
 yappi = None
 
 _FILENAME = os.path.join(constants.P_VDSM_RUN, 'vdsmd.prof')
 _FORMAT = config.get('vars', 'profile_format')
 
 
+class Error(Exception):
+    """ Raised when profiler is used incorrectly """
+
+
 def start():
-    global yappi
+    """ Starts application wide profiling """
     if is_enabled():
-        logging.debug("Starting profiling")
-        import yappi
-        yappi.start()
+        _start_profiling()
 
 
 def stop():
-    if is_running():
-        logging.debug("Stopping profiling")
-        yappi.stop()
-        stats = yappi.get_func_stats()
-        stats.save(_FILENAME, _FORMAT)
+    """ Stops application wide profiling """
+    if is_enabled() and is_running():
+        _stop_profiling(_FILENAME, _FORMAT)
 
 
 def is_enabled():
@@ -57,3 +58,38 @@
 
 def is_running():
     return yappi and yappi.is_running()
+
+
+def profile(filename, format=_FORMAT):
+    """
+    Profile decorated function, saving profile to filename using format.
+
+    Note: you cannot use this when the application wide profile is enabled, or
+    profile multiple functions in the same code path.
+    """
+    def decorator(f):
+        @wraps(f)
+        def wrapper(*a, **kw):
+            if is_running():
+                raise Error('Profiler is already running')
+            _start_profiling()
+            try:
+                return f(*a, **kw)
+            finally:
+                _stop_profiling(filename, format)
+        return wrapper
+    return decorator
+
+
+def _start_profiling():
+    global yappi
+    logging.debug("Starting profiling")
+    import yappi
+    yappi.start()
+
+
+def _stop_profiling(filename, format):
+    logging.debug("Stopping profiling")
+    yappi.stop()
+    stats = yappi.get_func_stats()
+    stats.save(filename, format)
diff --git a/tests/profileTests.py b/tests/profileTests.py
index 413a5be..5b5bee6 100644
--- a/tests/profileTests.py
+++ b/tests/profileTests.py
@@ -51,7 +51,7 @@
         raise SkipTest('yappi is not installed')
 
 
-class ApplicationProfileTests(VdsmTestCase):
+class ProfileTests(VdsmTestCase):
 
     def tearDown(self):
         try:
@@ -59,6 +59,9 @@
         except OSError as e:
             if e.errno != errno.ENOENT:
                 raise
+
+
+class ApplicationProfileTests(ProfileTests):
 
     @MonkeyPatch(profile, 'config', make_config(enable='true'))
     @MonkeyPatch(profile, '_FILENAME', FILENAME)
@@ -106,3 +109,39 @@
             self.assertFalse(profile.is_running())
         finally:
             profile.stop()
+
+
+class FunctionProfileTests(ProfileTests):
+
+    # Function profile must succeed if profile is disabled in config.
+    @MonkeyPatch(profile, 'config', make_config(enable='false'))
+    def test_profile_disabled(self):
+        requires_yappi()
+        self.profiled_function()
+        pstats.Stats(FILENAME)
+
+    # Function profile must fail if profile is enabled in config - we cannot
+    # use application wide profile and function profile in the same time.
+    @MonkeyPatch(profile, 'config', make_config(enable='true'))
+    @MonkeyPatch(profile, '_FILENAME', FILENAME)
+    def test_fail_if_Profile_is_running(self):
+        requires_yappi()
+        profile.start()
+        try:
+            self.assertRaises(profile.Error, self.profiled_function)
+        finally:
+            profile.stop()
+
+    # It is not possible to call a profiled function from a profiled function.
+    @MonkeyPatch(profile, 'config', make_config(enable='false'))
+    def test_fail_recursive_profile(self):
+        requires_yappi()
+        self.assertRaises(profile.Error, self.recursive_profile)
+
+    @profile.profile(FILENAME)
+    def profiled_function(self):
+        pass
+
+    @profile.profile(FILENAME)
+    def recursive_profile(self):
+        self.profiled_function()


-- 
To view, visit http://gerrit.ovirt.org/27389
To unsubscribe, visit http://gerrit.ovirt.org/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I2bc861832bbbda67937b5663f6cfbc1a54bfbf7d
Gerrit-PatchSet: 1
Gerrit-Project: vdsm
Gerrit-Branch: master
Gerrit-Owner: Nir Soffer <nsoffer at redhat.com>


More information about the vdsm-patches mailing list