1 # Copyright (c) Twisted Matrix Laboratories.
2 # See LICENSE for details.
5 Benchmarks comparing the write performance of a "normal" Protocol instance
6 and an instance of a Protocol class which has had L{twisted.conch.mixin}'s
7 L{BufferingMixin<twisted.conch.mixin.BufferingMixin>} mixed in to perform
8 Nagle-like write coalescing.
11 from sys import stdout
12 from pprint import pprint
15 from twisted.python.usage import Options
16 from twisted.python.log import startLogging
18 from twisted.internet.protocol import ServerFactory, Protocol, ClientCreator
19 from twisted.internet.defer import Deferred
20 from twisted.internet import reactor
22 from twisted.conch.mixin import BufferingMixin
25 class BufferingBenchmark(Options):
27 Options for configuring the execution parameters of a benchmark run.
32 'Work multiplier (bigger takes longer, might resist noise better)')]
34 def postOptions(self):
35 self['scale'] = int(self['scale'])
39 class ServerProtocol(Protocol):
41 A silent protocol which only waits for a particular amount of input and
42 then fires a Deferred.
44 def __init__(self, expected, finished):
45 self.expected = expected
46 self.finished = finished
49 def dataReceived(self, bytes):
50 self.expected -= len(bytes)
51 if self.expected == 0:
52 finished, self.finished = self.finished, None
53 finished.callback(None)
57 class BufferingProtocol(Protocol, BufferingMixin):
59 A protocol which uses the buffering mixin to provide a write method.
64 class UnbufferingProtocol(Protocol):
66 A protocol which provides a naive write method which simply passes through
70 def connectionMade(self):
72 Bind write to the transport's write method and flush to a no-op
73 function in order to provide the same API as is provided by
76 self.write = self.transport.write
77 self.flush = lambda: None
81 def _write(proto, byteCount):
85 for i in range(byteCount):
91 def _benchmark(byteCount, clientProtocol):
94 def cbFinished(ignored):
95 result[u'disconnected'] = time()
96 result[u'duration'] = result[u'disconnected'] - result[u'connected']
98 finished.addCallback(cbFinished)
101 f.protocol = lambda: ServerProtocol(byteCount, finished)
102 server = reactor.listenTCP(0, f)
104 f2 = ClientCreator(reactor, clientProtocol)
105 proto = f2.connectTCP('127.0.0.1', server.getHost().port)
106 def connected(proto):
107 result[u'connected'] = time()
109 proto.addCallback(connected)
110 proto.addCallback(_write, byteCount)
115 def _benchmarkBuffered(byteCount):
116 return _benchmark(byteCount, BufferingProtocol)
120 def _benchmarkUnbuffered(byteCount):
121 return _benchmark(byteCount, UnbufferingProtocol)
125 def benchmark(scale=1):
127 Benchmark and return information regarding the relative performance of a
128 protocol which does not use the buffering mixin and a protocol which
132 @param scale: A multipler to the amount of work to perform
134 @return: A Deferred which will fire with a dictionary mapping each of
135 the two unicode strings C{u'buffered'} and C{u'unbuffered'} to
136 dictionaries describing the performance of a protocol of each type.
137 These value dictionaries will map the unicode strings C{u'connected'}
138 and C{u'disconnected'} to the times at which each of those events
139 occurred and C{u'duration'} two the difference between these two values.
145 bufferedDeferred = _benchmarkBuffered(byteCount * scale)
146 def didBuffered(bufferedResult):
147 overallResult[u'buffered'] = bufferedResult
148 unbufferedDeferred = _benchmarkUnbuffered(byteCount * scale)
149 def didUnbuffered(unbufferedResult):
150 overallResult[u'unbuffered'] = unbufferedResult
152 unbufferedDeferred.addCallback(didUnbuffered)
153 return unbufferedDeferred
154 bufferedDeferred.addCallback(didBuffered)
155 return bufferedDeferred
161 Perform a single benchmark run, starting and stopping the reactor and
162 logging system as necessary.
166 options = BufferingBenchmark()
167 options.parseOptions(args)
169 d = benchmark(options['scale'])
170 def cbBenchmark(result):
172 def ebBenchmark(err):
173 print err.getTraceback()
174 d.addCallbacks(cbBenchmark, ebBenchmark)
175 def stopReactor(ign):
177 d.addBoth(stopReactor)
181 if __name__ == '__main__':