OSDN Git Service

merge in nyc-mr1-release history after reset to nyc-mr1-dev
[android-x86/system-extras.git] / tests / sdcard / plot_sdcard.py
1 #!/usr/bin/python2.5
2 #
3 # Copyright 2009, The Android Open Source Project
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 #     http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17
18 """plot_sdcard: A module to plot the results of an sdcard perf test.
19
20 Requires Gnuplot python v 1.8
21
22 Typical usage:
23  -t x axis is time
24  -i x axis is iteration
25  -p profile data generated by profile_sdcard.sh
26
27 ./plot_sdcard.py -t /tmp/data.txt
28 ./plot_sdcard.py -i /tmp/data.txt
29 ./plot_sdcard.py -p
30
31 python interpreter
32 >>> import plot_sdcard as p
33 >>> (metadata, data) = p.Parse('/tmp/data.txt')
34 >>> p.PlotIterations(metadata, data)
35 >>> p.PlotTimes(metadata, data)
36
37 """
38
39 import getopt
40 from itertools import izip
41 import re
42 import sys
43 import Gnuplot
44 import numpy
45
46
47 class DataSet(object):
48   """Dataset holds the summary and data (time,value pairs)."""
49
50   def __init__(self, line):
51     res = re.search(('# StopWatch ([\w]+) total/cumulative '
52                      'duration ([0-9.]+). Samples: ([0-9]+)'), line)
53     self.time = []
54     self.data = []
55     self.name = res.group(1)
56     self.duration = float(res.group(2))
57     self.iteration = int(res.group(3))
58     self.summary = re.match('([a-z_]+)_total', self.name)
59
60   def __repr__(self):
61     return str(zip(self.time, self.data))
62
63   def Add(self, time, value):
64     self.time.append(time)
65     self.data.append(value)
66
67   def RescaleTo(self, length):
68     factor = len(self.data) / length
69
70     if factor > 1:
71       new_time = []
72       new_data = []
73       accum = 0.0
74       idx = 1
75       for t, d in izip(self.time, self.data):
76         accum += d
77         if idx % factor == 0:
78           new_time.append(t)
79           new_data.append(accum / factor)
80           accum = 0
81         idx += 1
82       self.time = new_time
83       self.data = new_data
84
85
86 class Metadata(object):
87   def __init__(self):
88     self.kernel = ''
89     self.command_line = ''
90     self.sched = ''
91     self.name = ''
92     self.fadvise = ''
93     self.iterations = 0
94     self.duration = 0.0
95     self.complete = False
96
97   def Parse(self, line):
98     if line.startswith('# Kernel:'):
99       self.kernel = re.search('Linux version ([0-9.]+-[^ ]+)', line).group(1)
100     elif line.startswith('# Command:'):
101       self.command_line = re.search('# Command: [/\w_]+ (.*)', line).group(1)
102       self.command_line = self.command_line.replace(' --', '-')
103       self.command_line = self.command_line.replace(' -d', '')
104       self.command_line = self.command_line.replace('--test=', '')
105     elif line.startswith('# Iterations'):
106       self.iterations = int(re.search('# Iterations: ([0-9]+)', line).group(1))
107     elif line.startswith('# Fadvise'):
108       self.fadvise = re.search('# Fadvise: ([\w]+)', line).group(1)
109     elif line.startswith('# Sched'):
110       self.sched = re.search('# Sched features: ([\w]+)', line).group(1)
111       self.complete = True
112
113   def AsTitle(self):
114     return '%s-duration:%f\\n-%s\\n%s' % (
115         self.kernel, self.duration, self.command_line, self.sched)
116
117   def UpdateWith(self, dataset):
118     self.duration = max(self.duration, dataset.duration)
119     self.name = dataset.name
120
121
122 def Parse(filename):
123   """Parse a file with the collected data.
124
125   The data must be in 2 rows (x,y).
126
127   Args:
128     filename: Full path to the file.
129   """
130
131   f = open(filename, 'r')
132
133   metadata = Metadata()
134   data = []  # array of dataset
135   dataset = None
136
137   for num, line in enumerate(f):
138     try:
139       line = line.strip()
140       if not line: continue
141
142       if not metadata.complete:
143         metadata.Parse(line)
144         continue
145
146       if re.match('[a-z_]', line):
147         continue
148
149       if line.startswith('# StopWatch'):  # Start of a new dataset
150         if dataset:
151           if dataset.summary:
152             metadata.UpdateWith(dataset)
153           else:
154             data.append(dataset)
155
156         dataset = DataSet(line)
157         continue
158
159       if line.startswith('#'):
160         continue
161
162       # must be data at this stage
163       try:
164         (time, value) = line.split(None, 1)
165       except ValueError:
166         print 'skipping line %d: %s' % (num, line)
167         continue
168
169       if dataset and not dataset.summary:
170         dataset.Add(float(time), float(value))
171
172     except Exception:
173       print 'Error parsing line %d' % num, sys.exc_info()[0]
174       raise
175   data.append(dataset)
176   if not metadata.complete:
177     print """Error missing metadata. Did you mount debugfs?
178     [adb shell mount -t debugfs none /sys/kernel/debug]"""
179     sys.exit(1)
180   return (metadata, data)
181
182
183 def PlotIterations(metadata, data):
184   """Plot the duration of the ops against iteration.
185
186   If you are plotting data with widely different runtimes you probably want to
187   use PlotTimes instead.
188
189   For instance when readers and writers are in the same mix, the
190   readers will go thru 100 iterations much faster than the
191   writers. The load test tries to be smart about that but the final
192   iterations of the writers will likely be done w/o any influence from
193   the readers.
194
195   Args:
196     metadata: For the graph's title.
197     data: pair of to be plotted.
198   """
199
200   gp = Gnuplot.Gnuplot(persist=1)
201   gp('set data style lines')
202   gp.clear()
203   gp.xlabel('iterations')
204   gp.ylabel('duration in second')
205   gp.title(metadata.AsTitle())
206   styles = {}
207   line_style = 1
208
209   for dataset in data:
210     dataset.RescaleTo(metadata.iterations)
211     x = numpy.arange(len(dataset.data), dtype='int_')
212     if not dataset.name in styles:
213       styles[dataset.name] = line_style
214       line_style += 1
215       d = Gnuplot.Data(x, dataset.data,
216                        title=dataset.name,
217                        with_='lines ls %d' % styles[dataset.name])
218     else:  # no need to repeat a title that exists already.
219       d = Gnuplot.Data(x, dataset.data,
220                        with_='lines ls %d' % styles[dataset.name])
221
222     gp.replot(d)
223   gp.hardcopy('/tmp/%s-%s-%f.png' %
224               (metadata.name, metadata.kernel, metadata.duration),
225               terminal='png')
226
227
228 def PlotTimes(metadata, data):
229   """Plot the duration of the ops against time elapsed.
230
231   Args:
232     metadata: For the graph's title.
233     data: pair of to be plotted.
234   """
235
236   gp = Gnuplot.Gnuplot(persist=1)
237   gp('set data style impulses')
238   gp('set xtics 1')
239   gp.clear()
240   gp.xlabel('seconds')
241   gp.ylabel('duration in second')
242   gp.title(metadata.AsTitle())
243   styles = {}
244   line_style = 1
245
246   for dataset in data:
247     x = numpy.array(dataset.time, dtype='float_')
248     if not dataset.name in styles:
249       styles[dataset.name] = line_style
250       line_style += 1
251       d = Gnuplot.Data(x, dataset.data,
252                        title=dataset.name,
253                        with_='impulses ls %d' % styles[dataset.name])
254     else:  # no need to repeat a title that exists already.
255       d = Gnuplot.Data(x, dataset.data,
256                        with_='impulses ls %d' % styles[dataset.name])
257
258     gp.replot(d)
259   gp.hardcopy('/tmp/%s-%s-%f.png' %
260               (metadata.name, metadata.kernel, metadata.duration),
261               terminal='png')
262
263
264 def PlotProfile():
265   """Plot the time of a run against the number of processes."""
266   (metadata, data) = Parse('/tmp/sdcard-scalability.txt')
267   gp = Gnuplot.Gnuplot(persist=1)
268   gp('set data style impulses')
269   gp('set xtics 1')
270   gp('set pointsize 2')
271   gp.clear()
272   gp.xlabel('writer process')
273   gp.ylabel('duration in second')
274   gp.title(metadata.AsTitle())
275
276   dataset = data[0]
277   x = numpy.array(dataset.time, dtype='int_')
278   d = Gnuplot.Data(x, dataset.data,
279                    title=dataset.name,
280                    with_='linespoints')
281   gp.replot(d)
282   gp.hardcopy('/tmp/%s-%s-%f.png' %
283               (metadata.name, metadata.kernel, metadata.duration),
284               terminal='png')
285
286
287 def Usage():
288   """Print this module's usage."""
289   print """
290   To plot the result using the iter number of the x axis:
291
292     plot_sdcard.py -i /tmp/data.txt
293
294   To plot the result using time for the x axis:
295
296     plot_sdcard.py -t /tmp/data.txt
297
298   To plot the result from the profiler:
299
300     profile_sdcard.sh
301     plot_sdcard.py -p
302
303   """
304   sys.exit(2)
305
306
307 def main(argv):
308   try:
309     (optlist, args) = getopt.getopt(argv[1:],
310                                     'itp', ['iteration', 'time', 'profile'])
311   except getopt.GetoptError, err:
312     print str(err)
313     Usage()
314
315   for flag, val in optlist:
316     if flag in ('-i', '--iteration'):
317       (metadata, data) = Parse(args[0])
318       PlotIterations(metadata, data)
319       sys.exit(0)
320     elif flag in ('-t', '--time'):
321       (metadata, data) = Parse(args[0])
322       PlotTimes(metadata, data)
323       sys.exit(0)
324     elif flag in ('-p', '--profile'):
325       PlotProfile()
326       sys.exit(0)
327   Usage()
328
329
330 if __name__ == '__main__':
331   if Gnuplot.__version__ != "1.8":
332     print "Gnuplot should be 1.8. See REAME file"
333     sys.exit(2)
334   main(sys.argv)