launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #02761
[Merge] lp:~lifeless/launchpad/profile into lp:launchpad
Robert Collins has proposed merging lp:~lifeless/launchpad/profile into lp:launchpad.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~lifeless/launchpad/profile/+merge/51078
Allow overriding the profiling_allowed config entry via a feature flag. This will let us have profiling on staging for developers only. \o/
--
https://code.launchpad.net/~lifeless/launchpad/profile/+merge/51078
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~lifeless/launchpad/profile into lp:launchpad.
=== modified file 'lib/lp/services/features/flags.py'
--- lib/lp/services/features/flags.py 2011-02-20 21:15:57 +0000
+++ lib/lp/services/features/flags.py 2011-02-24 06:48:01 +0000
@@ -61,6 +61,10 @@
'boolean',
'Enables use of memcached where it is supported.',
'enabled'),
+ ('profiling.enabled',
+ 'boolean',
+ 'Overrides config.profiling.profiling_allowed to permit profiling.'
+ ''),
('publicrestrictedlibrarian',
'boolean',
'Redirects to private librarian files instead of proxying them.',
=== modified file 'lib/lp/services/profile/configure.zcml'
--- lib/lp/services/profile/configure.zcml 2010-08-26 02:00:51 +0000
+++ lib/lp/services/profile/configure.zcml 2011-02-24 06:48:01 +0000
@@ -7,6 +7,11 @@
xmlns:browser="http://namespaces.zope.org/browser">
<subscriber
+ for="zope.app.publication.interfaces.IBeforeTraverseEvent"
+ handler="lp.services.profile.profile.before_traverse"
+ />
+
+ <subscriber
handler="lp.services.profile.profile.start_request"
/>
=== modified file 'lib/lp/services/profile/profile.py'
--- lib/lp/services/profile/profile.py 2011-02-17 16:58:57 +0000
+++ lib/lp/services/profile/profile.py 2011-02-24 06:48:01 +0000
@@ -14,6 +14,7 @@
from bzrlib.lsprof import BzrProfiler
from chameleon.zpt.template import PageTemplateFile
+from zope.app.publication.interfaces import IBeforeTraverseEvent
from zope.app.publication.interfaces import IEndRequestEvent
from zope.component import (
adapter,
@@ -30,6 +31,7 @@
memory,
resident,
)
+from lp.services.features import getFeatureFlag
class ProfilingOops(Exception):
@@ -38,21 +40,49 @@
_profilers = threading.local()
+def before_traverse(event):
+ """Handle profiling when enabled via the profiling.enabled feature flag."""
+ # This event is raised on each step of traversal so needs to be lightweight
+ # and not assume that profiling has not started - but this is equally well
+ # done in _maybe_profile so that function takes care of it. We have to use
+ # this event (or add a new one) because we depend on the feature flags
+ # system being configured and usable, and on the principal being known.
+ if getFeatureFlag('profiling.enabled'):
+ _maybe_profile(event)
+
@adapter(IStartRequestEvent)
def start_request(event):
- """Handle profiling.
-
+ """Handle profiling when configured as permitted."""
+ if not config.profiling.profiling_allowed:
+ return
+ _maybe_profile(event)
+
+
+def _maybe_profile(event):
+ """Setup profiling as requested.
+
If profiling is enabled, start a profiler for this thread. If memory
profiling is requested, save the VSS and RSS.
+
+ If already profiling, this is a no-op.
"""
- if not config.profiling.profiling_allowed:
- return
+ try:
+ if _profilers.profiling:
+ # Already profiling - e.g. called in from both start_request and
+ # before_traverse, or by successive before_traverse on one request.
+ return
+ except AttributeError:
+ # The first call in on a new thread cannot be profiling at the start.
+ pass
+ print "doing the do", event.request['PATH_INFO']
actions = get_desired_profile_actions(event.request)
+ print "actions", event.request['PATH_INFO'], actions
if config.profiling.profile_all_requests:
actions.add('log')
_profilers.actions = actions
_profilers.profiler = None
+ _profilers.profiling = True
if actions:
if actions.difference(('help', )):
# If this assertion has reason to fail, we'll need to add code
@@ -71,14 +101,15 @@
@adapter(IEndRequestEvent)
def end_request(event):
"""If profiling is turned on, save profile data for the request."""
- if not config.profiling.profiling_allowed:
- return
try:
- actions = _profilers.actions
+ if not _profilers.profiling:
+ return
+ _profilers.profiling = False
except AttributeError:
# Some tests don't go through all the proper motions, like firing off
# a start request event. Just be quiet about it.
return
+ actions = _profilers.actions
del _profilers.actions
request = event.request
# Create a timestamp including milliseconds.
=== modified file 'lib/lp/services/profile/tests.py'
--- lib/lp/services/profile/tests.py 2010-10-22 10:24:18 +0000
+++ lib/lp/services/profile/tests.py 2011-02-24 06:48:01 +0000
@@ -16,7 +16,10 @@
from lp.testing import TestCase
from bzrlib.lsprof import BzrProfiler
-from zope.app.publication.interfaces import EndRequestEvent
+from zope.app.publication.interfaces import (
+ BeforeTraverseEvent,
+ EndRequestEvent,
+ )
from zope.component import getSiteManager
import canonical.launchpad.webapp.adapter as da
@@ -26,6 +29,8 @@
)
from canonical.launchpad.webapp.servers import LaunchpadTestRequest
from canonical.launchpad.webapp.interfaces import StartRequestEvent
+from canonical.testing import layers
+from lp.services.features.testing import FeatureFixture
from lp.services.profile import profile
EXAMPLE_HTML_START = '''\
@@ -69,22 +74,26 @@
memory_profile_log=memory_profile_log)
-class TestRequestStartHandler(BaseTest):
- """Tests for the start handler of the profiler integration.
-
- See lib/canonical/doc/profiling.txt for an end-user description of
- the functionality.
- """
+class TestCleanupProfiler(BaseTest):
+ """Add a tearDown that will cleanup the profiler if it is running."""
def tearDown(self):
"Do the usual tearDown, plus clean up the profiler object."
if getattr(profile._profilers, 'profiler', None) is not None:
profile._profilers.profiler.stop()
del profile._profilers.profiler
- for name in ('actions', 'memory_profile_start'):
+ for name in ('actions', 'memory_profile_start', 'profiling'):
if getattr(profile._profilers, name, None) is not None:
delattr(profile._profilers, name)
- TestCase.tearDown(self)
+ super(TestCleanupProfiler, self).tearDown()
+
+
+class TestRequestStartHandler(TestCleanupProfiler):
+ """Tests for the start handler of the profiler integration.
+
+ See lib/canonical/doc/profiling.txt for an end-user description of
+ the functionality.
+ """
def test_config_stops_profiling(self):
"""The ``profiling_allowed`` configuration should disable all
@@ -450,5 +459,21 @@
self.assertCleanProfilerState()
-def test_suite():
- return unittest.TestLoader().loadTestsFromName(__name__)
+class TestBeforeTraverseHandler(TestCleanupProfiler):
+
+ layer = layers.DatabaseFunctionalLayer
+
+ def test_can_enable_profiling_over_config(self):
+ # The flag profiling.enabled wins over a config that has
+ # disabled profiling. This permits the use of profiling on qastaging
+ # and similar systems.
+ self.pushProfilingConfig(
+ profiling_allowed='False', profile_all_requests='True',
+ memory_profile_log='.')
+ event = BeforeTraverseEvent(None,
+ self._get_request('/++profile++show,log'))
+ with FeatureFixture({'profiling.enabled':'on'}):
+ profile.before_traverse(event)
+ self.assertTrue(profile._profilers.profiling)
+ self.assertIsInstance(profile._profilers.profiler, BzrProfiler)
+ self.assertEquals(profile._profilers.actions, set(('show', 'log')))