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