Merge lp:~cjwatson/launchpad/optimise-bin-py into lp:launchpad

Proposed by Colin Watson on 2017-10-05
Status: Merged
Merged at revision: 18484
Proposed branch: lp:~cjwatson/launchpad/optimise-bin-py
Merge into: lp:launchpad
Diff against target: 444 lines (+104/-127)
8 files modified
lib/lp/services/config/__init__.py (+2/-2)
lib/lp/services/config/doc/canonical-config.txt (+4/-4)
lib/lp/services/log/logger.py (+6/-6)
lib/lp/services/webapp/initialization.py (+40/-1)
lib/lp/services/webapp/tests/test_errorlog.py (+0/-72)
lib/lp/services/webapp/tests/test_initialization.py (+46/-1)
lib/lp_sitecustomize.py (+3/-38)
versions.cfg (+3/-3)
To merge this branch: bzr merge lp:~cjwatson/launchpad/optimise-bin-py
Reviewer Review Type Date Requested Status
William Grant code 2017-10-05 Approve on 2017-10-19
Review via email: mp+331863@code.launchpad.net

Commit Message

Optimise lp_sitecustomize so that bin/py starts up more quickly.

Description of the Change

This takes bin/py from 0.76 seconds to 0.43 seconds on my system. (The difference is even more pronounced following https://code.launchpad.net/~cjwatson/launchpad/virtualenv-pip/+merge/331388, where importing pkg_resources is apparently a bit more expensive.)

https://code.launchpad.net/~cjwatson/lazr.config/faster-version/+merge/331833 and https://code.launchpad.net/~cjwatson/lazr.delegates/faster-version/+merge/331834 must land and be released first.

To post a comment you must log in.
William Grant (wgrant) :
review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== removed symlink 'bzrplugins/hg'
2=== target was u'../sourcecode/bzr-hg'
3=== modified file 'lib/lp/services/config/__init__.py'
4--- lib/lp/services/config/__init__.py 2016-10-04 11:19:07 +0000
5+++ lib/lp/services/config/__init__.py 2017-10-05 13:43:06 +0000
6@@ -1,4 +1,4 @@
7-# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
8+# Copyright 2009-2017 Canonical Ltd. This software is licensed under the
9 # GNU Affero General Public License version 3 (see the file LICENSE).
10
11 '''
12@@ -22,7 +22,6 @@
13
14 from lazr.config import ImplicitTypeSchema
15 from lazr.config.interfaces import ConfigErrors
16-import pkg_resources
17 import ZConfig
18
19 from lp.services.osutils import open_for_writing
20@@ -241,6 +240,7 @@
21
22 def _setZConfig(self):
23 """Modify the config, adding automatically generated settings"""
24+ import pkg_resources
25 schemafile = pkg_resources.resource_filename(
26 'zope.app.server', 'schema.xml')
27 schema = ZConfig.loadSchema(schemafile)
28
29=== modified file 'lib/lp/services/config/doc/canonical-config.txt'
30--- lib/lp/services/config/doc/canonical-config.txt 2015-10-01 22:36:23 +0000
31+++ lib/lp/services/config/doc/canonical-config.txt 2017-10-05 13:43:06 +0000
32@@ -59,7 +59,7 @@
33 '.../launchpad-lazr.conf'
34
35 >>> config.extends.filename
36- '.../launchpad-lazr.conf'
37+ u'.../launchpad-lazr.conf'
38
39 LaunchpadConfig provides __contains__ and __getitem__ to check and
40 access lazr.config sections and keys.
41@@ -160,7 +160,7 @@
42 '.../configs/testrunner/test-process-lazr.conf'
43
44 >>> test_config.extends.filename
45- '.../configs/testrunner/launchpad-lazr.conf'
46+ u'.../configs/testrunner/launchpad-lazr.conf'
47
48 >>> test_config.answertracker.days_before_expiration
49 300
50@@ -180,7 +180,7 @@
51 '.../configs/testrunner/launchpad-lazr.conf'
52
53 >>> test_config.extends.filename
54- '.../configs/development/launchpad-lazr.conf'
55+ u'.../configs/development/launchpad-lazr.conf'
56
57 >>> test_config.answertracker.days_before_expiration
58 15
59@@ -220,7 +220,7 @@
60 # >>> config.filename
61 # '.../configs/mailman-itests/launchpad-lazr.conf'
62 # >>> config.extends.filename
63-# '.../configs/development/launchpad-lazr.conf'
64+# u'.../configs/development/launchpad-lazr.conf'
65 # >>> config.database.dbname
66 # 'launchpad_dev'
67
68
69=== modified file 'lib/lp/services/log/logger.py'
70--- lib/lp/services/log/logger.py 2014-08-29 04:29:16 +0000
71+++ lib/lp/services/log/logger.py 2017-10-05 13:43:06 +0000
72@@ -1,4 +1,4 @@
73-# Copyright 2010-2011 Canonical Ltd. This software is licensed under the
74+# Copyright 2010-2017 Canonical Ltd. This software is licensed under the
75 # GNU Affero General Public License version 3 (see the file LICENSE).
76
77 """Loggers."""
78@@ -17,11 +17,6 @@
79 import sys
80 import traceback
81
82-from testtools.content import (
83- Content,
84- UTF8_TEXT,
85- )
86-
87 from lp.services.log import loglevels
88
89
90@@ -224,5 +219,10 @@
91 Use with `testtools.TestCase.addDetail`, `fixtures.Fixture.addDetail`,
92 and anything else that understands details.
93 """
94+ # Only import these here to avoid importing testtools outside tests.
95+ from testtools.content import (
96+ Content,
97+ UTF8_TEXT,
98+ )
99 get_bytes = lambda: [self.getLogBuffer().encode("utf-8")]
100 return Content(UTF8_TEXT, get_bytes)
101
102=== modified file 'lib/lp/services/webapp/initialization.py'
103--- lib/lp/services/webapp/initialization.py 2010-08-20 20:31:18 +0000
104+++ lib/lp/services/webapp/initialization.py 2017-10-05 13:43:06 +0000
105@@ -1,4 +1,4 @@
106-# Copyright 2010 Canonical Ltd. This software is licensed under the
107+# Copyright 2010-2017 Canonical Ltd. This software is licensed under the
108 # GNU Affero General Public License version 3 (see the file LICENSE).
109
110 """Initializes the application after ZCML has been processed."""
111@@ -8,15 +8,19 @@
112 getSiteManager,
113 )
114 from zope.interface import (
115+ alsoProvides,
116 implementer,
117 Interface,
118 )
119 from zope.processlifetime import IDatabaseOpened
120+import zope.publisher.browser
121 from zope.publisher.interfaces import IRequest
122 from zope.publisher.interfaces.browser import IBrowserRequest
123 from zope.publisher.interfaces.http import IHTTPRequest
124 from zope.traversing.interfaces import ITraversable
125
126+from lp.services.webapp.interfaces import IUnloggedException
127+
128
129 @implementer(Interface)
130 def adapter_mask(*args):
131@@ -37,6 +41,7 @@
132 Python first starts.
133 """
134 fix_up_namespace_traversers()
135+ customize_get_converter()
136
137
138 def fix_up_namespace_traversers():
139@@ -61,3 +66,37 @@
140 sm.registerAdapter(
141 adapter_mask,
142 required=(Interface, request_iface), name=name, info=info)
143+
144+
145+def customize_get_converter(zope_publisher_browser=zope.publisher.browser):
146+ """URL parameter conversion errors shouldn't generate an OOPS report.
147+
148+ This injects (monkey patches) our wrapper around get_converter so improper
149+ use of parameter type converters (like http://...?foo=bar:int) won't
150+ generate OOPS reports.
151+
152+ This is done in a function rather than in ZCML because zope.publisher
153+ doesn't provide fine enough control of this any other way.
154+ """
155+ original_get_converter = zope_publisher_browser.get_converter
156+
157+ def get_converter(*args, **kws):
158+ """Get a type converter but turn off OOPS reporting if it fails."""
159+ converter = original_get_converter(*args, **kws)
160+
161+ def wrapped_converter(v):
162+ try:
163+ return converter(v)
164+ except ValueError as e:
165+ # Mark the exception as not being OOPS-worthy.
166+ alsoProvides(e, IUnloggedException)
167+ raise
168+
169+ # The converter can be None, in which case wrapping it makes no sense,
170+ # otherwise it is a function which we wrap.
171+ if converter is None:
172+ return None
173+ else:
174+ return wrapped_converter
175+
176+ zope_publisher_browser.get_converter = get_converter
177
178=== modified file 'lib/lp/services/webapp/tests/test_errorlog.py'
179--- lib/lp/services/webapp/tests/test_errorlog.py 2017-09-02 13:29:14 +0000
180+++ lib/lp/services/webapp/tests/test_errorlog.py 2017-10-05 13:43:06 +0000
181@@ -13,7 +13,6 @@
182 from fixtures import TempDir
183 from lazr.batchnavigator.interfaces import InvalidBatchSizeError
184 from lazr.restful.declarations import error_status
185-from lp_sitecustomize import customize_get_converter
186 import oops_amqp
187 import pytz
188 import testtools
189@@ -611,77 +610,6 @@
190 self.assertTrue(report['ignore'])
191
192
193-class TestWrappedParameterConverter(testtools.TestCase):
194- """Make sure URL parameter type conversions don't generate OOPS reports"""
195-
196- def test_return_value_untouched(self):
197- # When a converter succeeds, its return value is passed through the
198- # wrapper untouched.
199-
200- class FauxZopePublisherBrowserModule:
201- def get_converter(self, type_):
202- def the_converter(value):
203- return 'converted %r to %s' % (value, type_)
204- return the_converter
205-
206- module = FauxZopePublisherBrowserModule()
207- customize_get_converter(module)
208- converter = module.get_converter('int')
209- self.assertEqual("converted '42' to int", converter('42'))
210-
211- def test_value_errors_marked(self):
212- # When a ValueError is raised by the wrapped converter, the exception
213- # is marked with IUnloggedException so the OOPS machinery knows that a
214- # report should not be logged.
215-
216- class FauxZopePublisherBrowserModule:
217- def get_converter(self, type_):
218- def the_converter(value):
219- raise ValueError
220- return the_converter
221-
222- module = FauxZopePublisherBrowserModule()
223- customize_get_converter(module)
224- converter = module.get_converter('int')
225- try:
226- converter(42)
227- except ValueError as e:
228- self.assertTrue(IUnloggedException.providedBy(e))
229-
230- def test_other_errors_not_marked(self):
231- # When an exception other than ValueError is raised by the wrapped
232- # converter, the exception is not marked with IUnloggedException an
233- # OOPS report will be created.
234-
235- class FauxZopePublisherBrowserModule:
236- def get_converter(self, type_):
237- def the_converter(value):
238- raise RuntimeError
239- return the_converter
240-
241- module = FauxZopePublisherBrowserModule()
242- customize_get_converter(module)
243- converter = module.get_converter('int')
244- try:
245- converter(42)
246- except RuntimeError as e:
247- self.assertFalse(IUnloggedException.providedBy(e))
248-
249- def test_none_is_not_wrapped(self):
250- # The get_converter function that we're wrapping can return None, in
251- # that case there's no function for us to wrap and we just return None
252- # as well.
253-
254- class FauxZopePublisherBrowserModule:
255- def get_converter(self, type_):
256- return None
257-
258- module = FauxZopePublisherBrowserModule()
259- customize_get_converter(module)
260- converter = module.get_converter('int')
261- self.assertTrue(converter is None)
262-
263-
264 class TestHooks(testtools.TestCase):
265
266 def test_attach_http_nonbasicvalue(self):
267
268=== modified file 'lib/lp/services/webapp/tests/test_initialization.py'
269--- lib/lp/services/webapp/tests/test_initialization.py 2012-01-01 02:58:52 +0000
270+++ lib/lp/services/webapp/tests/test_initialization.py 2017-10-05 13:43:06 +0000
271@@ -1,4 +1,4 @@
272-# Copyright 2010 Canonical Ltd. This software is licensed under the
273+# Copyright 2010-2017 Canonical Ltd. This software is licensed under the
274 # GNU Affero General Public License version 3 (see the file LICENSE).
275
276 """Tests post-zcml application initialization.
277@@ -7,10 +7,12 @@
278
279 from zope.component import getSiteManager
280 from zope.interface import Interface
281+from zope.publisher import browser
282 from zope.publisher.interfaces.browser import IBrowserRequest
283 from zope.traversing.interfaces import ITraversable
284
285 from lp.services.webapp.errorlog import OopsNamespace
286+from lp.services.webapp.interfaces import IUnloggedException
287 from lp.services.webapp.servers import LaunchpadTestRequest
288 from lp.testing import TestCase
289 from lp.testing.layers import FunctionalLayer
290@@ -55,3 +57,46 @@
291 pass
292 else:
293 self.assertNotEqual(factory, not_the_namespace_factory)
294+
295+
296+class TestWrappedParameterConverter(TestCase):
297+ """Make sure URL parameter type conversions don't generate OOPS reports"""
298+
299+ layer = FunctionalLayer
300+
301+ def test_return_value_untouched(self):
302+ # When a converter succeeds, its return value is passed through the
303+ # wrapper untouched.
304+ converter = browser.get_converter('int')
305+ self.assertEqual(42, converter('42'))
306+
307+ def test_value_errors_marked(self):
308+ # When a ValueError is raised by the wrapped converter, the exception
309+ # is marked with IUnloggedException so the OOPS machinery knows that a
310+ # report should not be logged.
311+ converter = browser.get_converter('int')
312+ try:
313+ converter('not an int')
314+ except ValueError as e:
315+ self.assertTrue(IUnloggedException.providedBy(e))
316+
317+ def test_other_errors_not_marked(self):
318+ # When an exception other than ValueError is raised by the wrapped
319+ # converter, the exception is not marked with IUnloggedException an
320+ # OOPS report will be created.
321+ class BadString:
322+ def __str__(self):
323+ raise RuntimeError
324+
325+ converter = browser.get_converter('string')
326+ try:
327+ converter(BadString())
328+ except RuntimeError as e:
329+ self.assertFalse(IUnloggedException.providedBy(e))
330+
331+ def test_none_is_not_wrapped(self):
332+ # The get_converter function that we're wrapping can return None, in
333+ # that case there's no function for us to wrap and we just return None
334+ # as well.
335+ converter = browser.get_converter('unregistered')
336+ self.assertIsNone(converter)
337
338=== modified file 'lib/lp_sitecustomize.py'
339--- lib/lp_sitecustomize.py 2014-08-29 05:52:23 +0000
340+++ lib/lp_sitecustomize.py 2017-10-05 13:43:06 +0000
341@@ -1,4 +1,4 @@
342-# Copyright 2009 Canonical Ltd. This software is licensed under the
343+# Copyright 2009-2017 Canonical Ltd. This software is licensed under the
344 # GNU Affero General Public License version 3 (see the file LICENSE).
345
346 # This file is imported by parts/scripts/sitecustomize.py, as set up in our
347@@ -10,20 +10,16 @@
348 import os
349 import warnings
350
351-from swiftclient import client as swiftclient
352 from twisted.internet.defer import (
353 Deferred,
354 DeferredList,
355 )
356-from zope.interface import alsoProvides
357-import zope.publisher.browser
358 from zope.security import checker
359
360 from lp.services.log import loglevels
361 from lp.services.log.logger import LaunchpadLogger
362 from lp.services.log.mappingfilter import MappingFilter
363 from lp.services.mime import customizeMimetypes
364-from lp.services.webapp.interfaces import IUnloggedException
365
366
367 def add_custom_loglevels():
368@@ -95,7 +91,8 @@
369 only does swiftclient then emit lots of noise, but it also turns
370 keystoneclient debugging on.
371 """
372- swiftclient.logger.setLevel(logging.INFO)
373+ swiftclient_logger = logging.getLogger('swiftclient')
374+ swiftclient_logger.setLevel(logging.INFO)
375
376
377 def silence_zcml_logger():
378@@ -175,37 +172,6 @@
379 silence_swiftclient_logger()
380
381
382-def customize_get_converter(zope_publisher_browser=zope.publisher.browser):
383- """URL parameter conversion errors shouldn't generate an OOPS report.
384-
385- This injects (monkey patches) our wrapper around get_converter so improper
386- use of parameter type converters (like http://...?foo=bar:int) won't
387- generate OOPS reports.
388- """
389- original_get_converter = zope_publisher_browser.get_converter
390-
391- def get_converter(*args, **kws):
392- """Get a type converter but turn off OOPS reporting if it fails."""
393- converter = original_get_converter(*args, **kws)
394-
395- def wrapped_converter(v):
396- try:
397- return converter(v)
398- except ValueError as e:
399- # Mark the exception as not being OOPS-worthy.
400- alsoProvides(e, IUnloggedException)
401- raise
402-
403- # The converter can be None, in which case wrapping it makes no sense,
404- # otherwise it is a function which we wrap.
405- if converter is None:
406- return None
407- else:
408- return wrapped_converter
409-
410- zope_publisher_browser.get_converter = get_converter
411-
412-
413 def main(instance_name):
414 # This is called by our custom buildout-generated sitecustomize.py
415 # in parts/scripts/sitecustomize.py. The instance name is sent to
416@@ -231,4 +197,3 @@
417 # through actually using itertools.groupby.
418 grouper = type(list(itertools.groupby([0]))[0][1])
419 checker.BasicTypes[grouper] = checker._iteratorChecker
420- customize_get_converter()
421
422=== modified file 'versions.cfg'
423--- versions.cfg 2017-09-25 12:24:39 +0000
424+++ versions.cfg 2017-10-05 13:43:06 +0000
425@@ -53,8 +53,8 @@
426 launchpadlib = 1.10.5
427 lazr.authentication = 0.1.1
428 lazr.batchnavigator = 1.2.11
429-lazr.config = 1.1.3
430-lazr.delegates = 2.0.3
431+lazr.config = 2.2.1
432+lazr.delegates = 2.0.4
433 lazr.enum = 1.1.3
434 lazr.jobrunner = 0.13
435 lazr.lifecycle = 1.1
436@@ -114,7 +114,7 @@
437 python-subunit = 0.0.8beta
438 python-swiftclient = 1.5.0
439 PyYAML = 3.10
440-pytz = 2016.4
441+pytz = 2017.2
442 rabbitfixture = 0.3.6
443 requests = 2.7.0
444 requests-toolbelt = 0.6.2