1 # Copyright (c) Twisted Matrix Laboratories.
2 # See LICENSE for details.
4 from twisted.test import proto_helpers, test_protocols
11 NETSTRING_PREFIX_TEMPLATE ="%d:"
12 NETSTRING_POSTFIX = ","
14 Usage: %s <number> <filename>
16 This script creates up to 2 ** <number> chunks with up to 2 **
17 <number> characters and sends them to the NetstringReceiver. The
18 sorted performance data for all combination is written to <filename>
21 You might want to start with a small number, maybe 10 or 12, and slowly
22 increase it. Stop when the performance starts to deteriorate ;-).
25 class PerformanceTester(object):
27 A class for testing the performance of some
34 def __init__(self, filename):
36 Initializes C{self.filename}.
38 If a file with this name already exists, asks if it should be
39 overwritten. Terminates with exit status 1, if the user does
42 if os.path.isfile(filename):
43 response = raw_input(("A file named %s exists. "
44 "Overwrite it (y/n)? ") % filename)
45 if response.lower() != "y":
46 print "Performance test cancelled."
48 self.filename = filename
51 def testPerformance(self, number):
53 Drives the execution of C{performTest} with arguments between
56 @param number: Defines the number of test runs to be performed.
59 for iteration in xrange(number):
60 self.performTest(iteration)
63 def performTest(self, iteration):
65 Performs one test iteration. Overwrite this.
67 @param iteration: The iteration number. Can be used to configure
69 @type iteration: C{int}
70 @raise NotImplementedError: because this method has to be implemented
73 raise NotImplementedError
76 def createReport(self):
78 Creates a file and writes a table with performance data.
80 The performance data are ordered by the total size of the netstrings.
81 In addition they show the chunk size, the number of chunks and the
82 time (in seconds) that elapsed while the C{NetstringReceiver}
83 received the netstring.
85 @param filename: The name of the report file that will be written.
86 @type filename: C{str}
88 self.outputFile = open(self.filename, "w")
90 self.writePerformanceData()
91 self.writeLineSeparator()
92 print "The report was written to %s." % self.filename
95 def writeHeader(self):
97 Writes the table header for the report.
99 self.writeLineSeparator()
100 self.outputFile.write("| %s |\n" % (" | ".join(self.headers),))
101 self.writeLineSeparator()
104 def writeLineSeparator(self):
106 Writes a 'line separator' made from '+' and '-' characters.
108 dashes = ("-" * (len(header) + 2) for header in self.headers)
109 self.outputFile.write("+%s+\n" % "+".join(dashes))
112 def writePerformanceData(self):
114 Writes one line for each item in C{self.performanceData}.
116 for combination, elapsed in sorted(self.performanceData.iteritems()):
117 totalSize, chunkSize, numberOfChunks = combination
118 self.outputFile.write(self.lineFormat %
119 (totalSize, chunkSize, numberOfChunks,
124 class NetstringPerformanceTester(PerformanceTester):
126 A class for determining the C{NetstringReceiver.dataReceived} performance.
128 Instantiates a C{NetstringReceiver} and calls its
129 C{dataReceived()} method with different chunks sizes and numbers
130 of chunks. Presents a table showing the relation between input
131 data and time to process them.
134 headers = ["Chunk size", "Number of chunks", "Total size",
136 lineFormat = ("| %%%dd | %%%dd | %%%dd | %%%d.4f |\n" %
137 tuple([len(header) for header in headers]))
139 def __init__(self, filename):
141 Sets up the output file and the netstring receiver that will be
142 used for receiving data.
144 @param filename: The name of the file for storing the report.
145 @type filename: C{str}
147 PerformanceTester.__init__(self, filename)
148 transport = proto_helpers.StringTransport()
149 self.netstringReceiver = test_protocols.TestNetstring()
150 self.netstringReceiver.makeConnection(transport)
153 def performTest(self, number):
155 Tests the performance of C{NetstringReceiver.dataReceived}.
157 Feeds netstrings of various sizes in different chunk sizes
158 to a C{NetstringReceiver} and stores the elapsed time in
159 C{self.performanceData}.
161 @param number: The maximal chunks size / number of
162 chunks to be checked.
165 chunkSize = 2 ** number
166 numberOfChunks = chunkSize
167 while numberOfChunks:
168 self.testCombination(chunkSize, numberOfChunks)
169 numberOfChunks = numberOfChunks // 2
172 def testCombination(self, chunkSize, numberOfChunks):
174 Tests one combination of chunk size and number of chunks.
176 @param chunkSize: The size of one chunk to be sent to the
177 C{NetstringReceiver}.
178 @type chunkSize: C{int}
179 @param numberOfChunks: The number of C{chunkSize}-sized chunks to
180 be sent to the C{NetstringReceiver}.
181 @type numberOfChunks: C{int}
183 chunk, dataSize = self.configureCombination(chunkSize, numberOfChunks)
184 elapsed = self.receiveData(chunk, numberOfChunks, dataSize)
185 key = (chunkSize, numberOfChunks, dataSize)
186 self.performanceData[key] = elapsed
189 def configureCombination(self, chunkSize, numberOfChunks):
191 Updates C{MAX_LENGTH} for {self.netstringReceiver} (to avoid
192 C{NetstringParseErrors} that might be raised if the size
193 exceeds the default C{MAX_LENGTH}).
195 Calculates and returns one 'chunk' of data and the total size
198 @param chunkSize: The size of chunks that will be received.
199 @type chunkSize: C{int}
200 @param numberOfChunks: The number of C{chunkSize}-sized chunks
201 that will be received.
202 @type numberOfChunks: C{int}
204 @return: A tuple consisting of string of C{chunkSize} 'a'
205 characters and the size of the netstring data portion.
207 chunk = "a" * chunkSize
208 dataSize = chunkSize * numberOfChunks
209 self.netstringReceiver.MAX_LENGTH = dataSize
210 numberOfDigits = math.ceil(math.log10(dataSize)) + 1
211 return chunk, dataSize
214 def receiveData(self, chunk, numberOfChunks, dataSize):
215 dr = self.netstringReceiver.dataReceived
217 dr(NETSTRING_PREFIX_TEMPLATE % (dataSize,))
218 for idx in xrange(numberOfChunks):
220 dr(NETSTRING_POSTFIX)
221 elapsed = time.time() - now
222 assert self.netstringReceiver.received, "Didn't receive string!"
226 def disableGarbageCollector():
228 print 'Disabled Garbage Collector.'
231 def main(number, filename):
232 disableGarbageCollector()
233 npt = NetstringPerformanceTester(filename)
234 npt.testPerformance(int(number))
238 if __name__ == "__main__":
239 if len(sys.argv) < 3:
240 print USAGE % sys.argv[0]