Merge lp:~mbp/bzr/340347-log-decorator into lp:~bzr/bzr/trunk-old

Proposed by Martin Pool
Status: Merged
Merged at revision: not available
Proposed branch: lp:~mbp/bzr/340347-log-decorator
Merge into: lp:~bzr/bzr/trunk-old
Diff against target: 230 lines
To merge this branch: bzr merge lp:~mbp/bzr/340347-log-decorator
Reviewer Review Type Date Requested Status
John A Meinel Approve
Review via email: mp+8075@code.launchpad.net
To post a comment you must log in.
Revision history for this message
Martin Pool (mbp) wrote :

Fixes this bug https://bugs.edge.launchpad.net/bzr/+bug/340347 in both ways: pack.py no longer insists on getting an iterator (because the readv docstring says it may be a list) and TransportLogDecorator gives back a generator if the underlying transport gives one. I added a test for the second; the first is not super easily testable and imo does not need one as it's a debugging aid that's failing.

One variable was named 'length' when it should be 'offset' so I fixed it.

Some errors for readv now give more information which may help with things like bug 360476.

I cut out the rate and time display from the log+ output becasue they're now redundant.

Revision history for this message
John A Meinel (jameinel) wrote :

        + """Construct a new ReadVFile wrapper.
44 +
45 + :seealso: make_readv_reader
46 +
47 + :param readv_result: the most recent readv result - list or generator
48 + """
49 + # we rely on its state as a generator to keep track of how much has
50 + # been used.
51 + if not getattr(readv_result, 'next'):
52 + readv_result = iter(readv_result)

You can just use:

readv_result = iter(readv_result)

If the object *is* an iterator it will just return self.

>>> def foo():
... for x in xrange(10):
... yield x
...
>>> f= foo()
>>> f
<generator object at 0x0238F0F8>
>>> y = iter(f)
>>> y
<generator object at 0x0238F0F8>
>>> iter(f) is f
True

I think it simplifies the code a bit.

224 def readv(self, relpath, offsets, adjust_for_latency=False,
225 upper_limit=None):
226 + # we override at the readv() level rather than _readv() so that any
227 + # latency adjustments will be done by the underlying transport
228 """See Transport.readv."""
229 self._trace(('readv', relpath, offsets, adjust_for_latency,
230 upper_limit))

^- A comment should come after the docstring IMO

Anyway, the changes seem fine. I would just do an unconditional iter(foo) to make it cleaner.

review: Approve
Revision history for this message
Martin Pool (mbp) wrote :

> ^- A comment should come after the docstring IMO
>
> Anyway, the changes seem fine. I would just do an unconditional iter(foo) to
> make it cleaner.

Good point. I had the idea it made a new object rather than returning foo.

Thanks

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'NEWS'
2--- NEWS 2009-07-01 19:06:14 +0000
3+++ NEWS 2009-07-02 04:35:10 +0000
4@@ -75,6 +75,13 @@
5 transforms.
6 (Craig Hewetson, Martin Pool, #218206)
7
8+* The ``log+`` decorator, useful in debugging or profiling, could cause
9+ "AttributeError: 'list' object has no attribute 'next'". This is now
10+ fixed. The log decorator no longer shows the elapsed time or transfer
11+ rate because they're available in the log prefixes and the transport
12+ activity display respectively.
13+ (Martin Pool, #340347)
14+
15 * Progress bars are now suppressed again when the environment variable
16 ``BZR_PROGRESS_BAR`` is set to ``none``.
17 (Martin Pool, #339385)
18
19=== modified file 'bzrlib/pack.py'
20--- bzrlib/pack.py 2009-06-20 01:17:38 +0000
21+++ bzrlib/pack.py 2009-07-02 04:35:10 +0000
22@@ -1,4 +1,4 @@
23-# Copyright (C) 2007 Canonical Ltd
24+# Copyright (C) 2007, 2009 Canonical Ltd
25 #
26 # This program is free software; you can redistribute it and/or modify
27 # it under the terms of the GNU General Public License as published by
28@@ -159,17 +159,32 @@
29
30
31 class ReadVFile(object):
32- """Adapt a readv result iterator to a file like protocol."""
33+ """Adapt a readv result iterator to a file like protocol.
34+
35+ The readv result must support the iterator protocol returning (offset,
36+ data_bytes) pairs.
37+ """
38+
39+ # XXX: This could be a generic transport class, as other code may want to
40+ # gradually consume the readv result.
41
42 def __init__(self, readv_result):
43+ """Construct a new ReadVFile wrapper.
44+
45+ :seealso: make_readv_reader
46+
47+ :param readv_result: the most recent readv result - list or generator
48+ """
49+ # readv can return a sequence or an iterator, but we require an
50+ # iterator to know how much has been consumed.
51+ readv_result = iter(readv_result)
52 self.readv_result = readv_result
53- # the most recent readv result block
54 self._string = None
55
56 def _next(self):
57 if (self._string is None or
58 self._string.tell() == self._string_length):
59- length, data = self.readv_result.next()
60+ offset, data = self.readv_result.next()
61 self._string_length = len(data)
62 self._string = StringIO(data)
63
64@@ -177,7 +192,9 @@
65 self._next()
66 result = self._string.read(length)
67 if len(result) < length:
68- raise errors.BzrError('request for too much data from a readv hunk.')
69+ raise errors.BzrError('wanted %d bytes but next '
70+ 'hunk only contains %d: %r...' %
71+ (length, len(result), result[:20]))
72 return result
73
74 def readline(self):
75@@ -185,7 +202,8 @@
76 self._next()
77 result = self._string.readline()
78 if self._string.tell() == self._string_length and result[-1] != '\n':
79- raise errors.BzrError('short readline in the readvfile hunk.')
80+ raise errors.BzrError('short readline in the readvfile hunk: %r'
81+ % (readline, ))
82 return result
83
84
85
86=== modified file 'bzrlib/tests/test_transport_log.py'
87--- bzrlib/tests/test_transport_log.py 2009-03-23 14:59:43 +0000
88+++ bzrlib/tests/test_transport_log.py 2009-07-02 04:35:11 +0000
89@@ -1,4 +1,4 @@
90-# Copyright (C) 2004, 2005, 2006, 2007 Canonical Ltd
91+# Copyright (C) 2004, 2005, 2006, 2007, 2009 Canonical Ltd
92 #
93 # This program is free software; you can redistribute it and/or modify
94 # it under the terms of the GNU General Public License as published by
95@@ -18,9 +18,13 @@
96 """Tests for log+ transport decorator."""
97
98
99+import types
100+
101+
102 from bzrlib.tests import TestCaseWithMemoryTransport
103 from bzrlib.trace import mutter
104 from bzrlib.transport import get_transport
105+from bzrlib.transport.log import TransportLogDecorator
106
107
108 class TestTransportLog(TestCaseWithMemoryTransport):
109@@ -41,4 +45,35 @@
110 # and they operate on the underlying transport
111 self.assertTrue(base_transport.has('subdir'))
112
113-
114+ def test_log_readv(self):
115+ # see <https://bugs.launchpad.net/bzr/+bug/340347>
116+
117+ # transports are not required to return a generator, but we
118+ # specifically want to check that those that do cause it to be passed
119+ # through, for the sake of minimum interference
120+ base_transport = DummyReadvTransport()
121+ # construct it directly to avoid needing the dummy transport to be
122+ # registered etc
123+ logging_transport = TransportLogDecorator(
124+ 'log+dummy:///', _decorated=base_transport)
125+
126+ result = base_transport.readv('foo', [(0, 10)])
127+ # sadly there's no types.IteratorType, and GeneratorType is too
128+ # specific
129+ self.assertTrue(getattr(result, 'next'))
130+
131+ result = logging_transport.readv('foo', [(0, 10)])
132+ self.assertTrue(getattr(result, 'next'))
133+ self.assertEquals(list(result),
134+ [(0, 'abcdefghij')])
135+
136+
137+class DummyReadvTransport(object):
138+
139+ base = 'dummy:///'
140+
141+ def readv(self, filename, offset_length_pairs):
142+ yield (0, 'abcdefghij')
143+
144+ def abspath(self, path):
145+ return self.base + path
146
147=== modified file 'bzrlib/transport/log.py'
148--- bzrlib/transport/log.py 2009-03-23 14:59:43 +0000
149+++ bzrlib/transport/log.py 2009-07-02 04:35:11 +0000
150@@ -44,6 +44,9 @@
151 and may be slow.
152
153 Not all operations are logged yet.
154+
155+ See also TransportTraceDecorator, that records a machine-readable log in
156+ memory for eg testing.
157 """
158
159 def __init__(self, *args, **kw):
160@@ -104,10 +107,15 @@
161 def _show_result(self, before, methodname, result):
162 result_len = None
163 if isinstance(result, types.GeneratorType):
164- # eagerly pull in all the contents, so that we can measure how
165- # long it takes to get them. this does make the behaviour a bit
166- # different, but we hope not noticably so
167+ # We now consume everything from the generator so that we can show
168+ # the results and the time it took to get them. However, to keep
169+ # compatibility with callers that may specifically expect a result
170+ # (see <https://launchpad.net/bugs/340347>) we also return a new
171+ # generator, reset to the starting position.
172 result = list(result)
173+ return_result = iter(result)
174+ else:
175+ return_result = result
176 if isinstance(result, (cStringIO.OutputType, StringIO.StringIO)):
177 val = repr(result.getvalue())
178 result_len = len(val)
179@@ -122,16 +130,17 @@
180 else:
181 shown_result = self._shorten(self._strip_tuple_parens(result))
182 mutter(" --> %s" % shown_result)
183- # XXX: the time may be wrong when e.g. a generator object is returned from
184- # an http readv, if the object is returned before the bulk data
185- # is read.
186- elapsed = time.time() - before
187- if result_len and elapsed > 0:
188- # this is the rate of higher-level data, not the raw network speed
189- mutter(" %9.03fs %8dKB/s" % (elapsed, result_len/elapsed/1024))
190- else:
191- mutter(" %9.03fs" % (elapsed))
192- return result
193+ # The log decorator no longer shows the elapsed time or transfer rate
194+ # because they're available in the log prefixes and the transport
195+ # activity display respectively.
196+ if False:
197+ elapsed = time.time() - before
198+ if result_len and elapsed > 0:
199+ # this is the rate of higher-level data, not the raw network speed
200+ mutter(" %9.03fs %8dKB/s" % (elapsed, result_len/elapsed/1024))
201+ else:
202+ mutter(" %9.03fs" % (elapsed))
203+ return return_result
204
205 def _shorten(self, x):
206 if len(x) > 70:
207
208=== modified file 'bzrlib/transport/trace.py'
209--- bzrlib/transport/trace.py 2009-03-23 14:59:43 +0000
210+++ bzrlib/transport/trace.py 2009-07-02 04:35:11 +0000
211@@ -33,6 +33,9 @@
212 Not all operations are logged at this point, if you need an unlogged
213 operation please add a test to the tests of this transport, for the logging
214 of the operation you want logged.
215+
216+ See also TransportLogDecorator, that records a machine-readable log in
217+ memory for eg testing.
218 """
219
220 def __init__(self, url, _decorated=None, _from_transport=None):
221@@ -126,7 +129,8 @@
222
223 def readv(self, relpath, offsets, adjust_for_latency=False,
224 upper_limit=None):
225- """See Transport.readv."""
226+ # we override at the readv() level rather than _readv() so that any
227+ # latency adjustments will be done by the underlying transport
228 self._trace(('readv', relpath, offsets, adjust_for_latency,
229 upper_limit))
230 return self._decorated.readv(relpath, offsets, adjust_for_latency,