Merge ~twom/launchpad:stats-request-timings into launchpad:master

Proposed by Tom Wardill
Status: Merged
Approved by: Tom Wardill
Approved revision: 37e530328474679636a5bf968d67775f50b0732e
Merge reported by: Otto Co-Pilot
Merged at revision: not available
Proposed branch: ~twom/launchpad:stats-request-timings
Merge into: launchpad:master
Diff against target: 187 lines (+106/-7)
2 files modified
lib/lp/services/webapp/publication.py (+29/-7)
lib/lp/services/webapp/tests/test_publication.py (+77/-0)
Reviewer Review Type Date Requested Status
Colin Watson (community) Approve
Review via email: mp+390256@code.launchpad.net

Commit message

Add traversal and publication duration timings

Description of the change

Use the existing duration timing mechanisms, but output to statsd as well.

To post a comment you must log in.
Revision history for this message
Colin Watson (cjwatson) wrote :

This would probably be a lot more useful if we added a tag with the pageid too - could you add that?

bfe59f2... by Tom Wardill

Add publication timings with pageid

Revision history for this message
Tom Wardill (twom) wrote :

> This would probably be a lot more useful if we added a tag with the pageid too
> - could you add that?

We don't have pageid in the traversal timings as it's too early, but we do in the publication timings which I've just added.

Revision history for this message
Colin Watson (cjwatson) wrote :

I'd really like to see some kind of test for this.

I don't think there's a strong reason we couldn't move the calculation of launchpad.pageid to afterTraversal; logically we should have everything we need for that as soon as traversal is complete. Maybe worth a try?

review: Approve
96347f1... by Tom Wardill

Move pageid calculation to afterTraversal

1210f6c... by Tom Wardill

Add test

Revision history for this message
Colin Watson (cjwatson) wrote :

Could we have a test for each of the failed-traversal and failed-publication cases too? Otherwise this LGTM.

review: Approve
Revision history for this message
Colin Watson (cjwatson) wrote :

Also the commit message on this MP is a bit incomplete now; maybe "Add traversal and publication duration timings"?

37e5303... by Tom Wardill

Add tests for failed traversal and failed publication

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/lib/lp/services/webapp/publication.py b/lib/lp/services/webapp/publication.py
2index a289f44..b76eec7 100644
3--- a/lib/lp/services/webapp/publication.py
4+++ b/lib/lp/services/webapp/publication.py
5@@ -427,13 +427,8 @@ class LaunchpadBrowserPublication(
6 request.setInWSGIEnvironment(
7 'launchpad.userid', request.principal.id)
8
9- # The view may be security proxied
10- view = removeSecurityProxy(ob)
11- # It's possible that the view is a bound method.
12- view = getattr(view, '__self__', view)
13- context = removeSecurityProxy(getattr(view, 'context', None))
14- pageid = self.constructPageID(view, context)
15- request.setInWSGIEnvironment('launchpad.pageid', pageid)
16+ # pageid is calculated at `afterTraversal`
17+ pageid = request._orig_env['launchpad.pageid']
18 # And spit the pageid out to our tracelog.
19 tracelog(request, 'p', pageid)
20
21@@ -485,6 +480,11 @@ class LaunchpadBrowserPublication(
22 publication_thread_duration = None
23 request.setInWSGIEnvironment(
24 'launchpad.publicationduration', publication_duration)
25+ # Update statsd, timing is in milliseconds
26+ getUtility(IStatsdClient).timing(
27+ 'publication_duration,success=True,pageid={}'.format(
28+ request._orig_env.get('launchpad.pageid')),
29+ publication_duration * 1000)
30
31 # Calculate SQL statement statistics.
32 sql_statements = da.get_request_statements()
33@@ -562,12 +562,24 @@ class LaunchpadBrowserPublication(
34 # Log the URL including vhost information to the ZServer tracelog.
35 tracelog(request, 'u', request.getURL())
36
37+ # The view may be security proxied
38+ view = removeSecurityProxy(ob)
39+ # It's possible that the view is a bound method.
40+ view = getattr(view, '__self__', view)
41+ context = removeSecurityProxy(getattr(view, 'context', None))
42+ pageid = self.constructPageID(view, context)
43+ request.setInWSGIEnvironment('launchpad.pageid', pageid)
44+
45 assert hasattr(request, '_traversal_start'), (
46 'request._traversal_start, which should have been set by '
47 'beforeTraversal(), was not found.')
48 traversal_duration = time.time() - request._traversal_start
49 request.setInWSGIEnvironment(
50 'launchpad.traversalduration', traversal_duration)
51+ # Update statsd, timing is in milliseconds
52+ getUtility(IStatsdClient).timing(
53+ 'traversal_duration,success=True,pageid={}'.format(pageid),
54+ traversal_duration * 1000)
55 if request._traversal_thread_start is not None:
56 traversal_thread_duration = (
57 _get_thread_time() - request._traversal_thread_start)
58@@ -600,6 +612,11 @@ class LaunchpadBrowserPublication(
59 publication_duration = now - request._publication_start
60 request.setInWSGIEnvironment(
61 'launchpad.publicationduration', publication_duration)
62+ # Update statsd, timing is in milliseconds
63+ getUtility(IStatsdClient).timing(
64+ 'publication_duration,success=False,pageid={}'.format(
65+ request._orig_env.get('launchpad.pageid')),
66+ publication_duration * 1000)
67 if thread_now is not None:
68 publication_thread_duration = (
69 thread_now - request._publication_thread_start)
70@@ -612,6 +629,11 @@ class LaunchpadBrowserPublication(
71 traversal_duration = now - request._traversal_start
72 request.setInWSGIEnvironment(
73 'launchpad.traversalduration', traversal_duration)
74+ # Update statsd, timing is in milliseconds
75+ getUtility(IStatsdClient).timing(
76+ 'traversal_duration,success=False,pageid={}'.format(
77+ request._orig_env.get('launchpad.pageid')
78+ ), traversal_duration * 1000)
79 if thread_now is not None:
80 traversal_thread_duration = (
81 thread_now - request._traversal_thread_start)
82diff --git a/lib/lp/services/webapp/tests/test_publication.py b/lib/lp/services/webapp/tests/test_publication.py
83index 335f999..fa9e9b4 100644
84--- a/lib/lp/services/webapp/tests/test_publication.py
85+++ b/lib/lp/services/webapp/tests/test_publication.py
86@@ -19,6 +19,11 @@ from storm.database import (
87 STATE_RECONNECT,
88 )
89 from storm.exceptions import DisconnectionError
90+from testtools.matchers import (
91+ Equals,
92+ GreaterThan,
93+ MatchesListwise,
94+ )
95 from zope.component import getUtility
96 from zope.interface import directlyProvides
97 from zope.publisher.interfaces import (
98@@ -32,6 +37,7 @@ from lp.services.oauth.interfaces import (
99 IOAuthConsumerSet,
100 IOAuthSignedRequest,
101 )
102+from lp.services.statsd.tests import StatsMixin
103 import lp.services.webapp.adapter as dbadapter
104 from lp.services.webapp.interfaces import (
105 NoReferrerError,
106@@ -55,6 +61,7 @@ from lp.testing import (
107 TestCase,
108 TestCaseWithFactory,
109 )
110+from lp.testing.fakemethod import FakeMethod
111 from lp.testing.layers import DatabaseFunctionalLayer
112
113
114@@ -299,3 +306,73 @@ class TestUnicodePath(TestCaseWithFactory):
115 browser.open,
116 'http://launchpad.test/%ED%B4%B5')
117 self.assertEqual(0, len(self.oopses))
118+
119+
120+class TestPublisherStats(StatsMixin, TestCaseWithFactory):
121+
122+ layer = DatabaseFunctionalLayer
123+
124+ def setUp(self):
125+ super(TestPublisherStats, self).setUp()
126+ self.setUpStats()
127+
128+ def test_traversal_stats(self):
129+ self.useFixture(FakeLogger())
130+ browser = self.getUserBrowser()
131+ browser.open('http://launchpad.test')
132+ self.assertEqual(2, self.stats_client.timing.call_count)
133+ self.assertThat(
134+ [x[0] for x in self.stats_client.timing.call_args_list],
135+ MatchesListwise(
136+ [MatchesListwise(
137+ (Equals('traversal_duration,success=True,'
138+ 'pageid=RootObject:index.html'),
139+ GreaterThan(0))),
140+ MatchesListwise(
141+ (Equals('publication_duration,success=True,'
142+ 'pageid=RootObject:index.html'),
143+ GreaterThan(0)))]))
144+
145+ def test_traversal_failure_stats(self):
146+ self.useFixture(FakeLogger())
147+ browser = self.getUserBrowser()
148+ self.patch(
149+ LaunchpadBrowserPublication,
150+ 'afterTraversal',
151+ FakeMethod(failure=Exception))
152+ self.assertRaises(
153+ Exception,
154+ browser.open,
155+ 'http://launchpad.test/')
156+ self.assertEqual(1, self.stats_client.timing.call_count)
157+ self.assertThat(
158+ [x[0] for x in self.stats_client.timing.call_args_list],
159+ MatchesListwise(
160+ [MatchesListwise(
161+ (Equals('traversal_duration,success=False,'
162+ 'pageid=None'),
163+ GreaterThan(0)))]))
164+
165+ def test_publication_failure_stats(self):
166+ self.useFixture(FakeLogger())
167+ browser = self.getUserBrowser()
168+ self.patch(
169+ dbadapter,
170+ 'set_permit_timeout_from_features',
171+ FakeMethod(failure=Exception))
172+ self.assertRaises(
173+ Exception,
174+ browser.open,
175+ 'http://launchpad.test/')
176+ self.assertEqual(2, self.stats_client.timing.call_count)
177+ self.assertThat(
178+ [x[0] for x in self.stats_client.timing.call_args_list],
179+ MatchesListwise(
180+ [MatchesListwise(
181+ (Equals('traversal_duration,success=True,'
182+ 'pageid=RootObject:index.html'),
183+ GreaterThan(0))),
184+ MatchesListwise(
185+ (Equals('publication_duration,success=False,'
186+ 'pageid=RootObject:index.html'),
187+ GreaterThan(0)))]))

Subscribers

People subscribed via source and target branches

to status/vote changes: