3 # Copyright 2009, The Android Open Source Project
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
9 # http://www.apache.org/licenses/LICENSE-2.0
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.
18 """plot_sdcard: A module to plot the results of an sdcard perf test.
20 Requires Gnuplot python v 1.8
24 -i x axis is iteration
25 -p profile data generated by profile_sdcard.sh
27 ./plot_sdcard.py -t /tmp/data.txt
28 ./plot_sdcard.py -i /tmp/data.txt
32 >>> import plot_sdcard as p
33 >>> (metadata, data) = p.Parse('/tmp/data.txt')
34 >>> p.PlotIterations(metadata, data)
35 >>> p.PlotTimes(metadata, data)
40 from itertools import izip
47 class DataSet(object):
48 """Dataset holds the summary and data (time,value pairs)."""
50 def __init__(self, line):
51 res = re.search(('# StopWatch ([\w]+) total/cumulative '
52 'duration ([0-9.]+). Samples: ([0-9]+)'), line)
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)
61 return str(zip(self.time, self.data))
63 def Add(self, time, value):
64 self.time.append(time)
65 self.data.append(value)
67 def RescaleTo(self, length):
68 factor = len(self.data) / length
75 for t, d in izip(self.time, self.data):
79 new_data.append(accum / factor)
86 class Metadata(object):
89 self.command_line = ''
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)
114 return '%s-duration:%f\\n-%s\\n%s' % (
115 self.kernel, self.duration, self.command_line, self.sched)
117 def UpdateWith(self, dataset):
118 self.duration = max(self.duration, dataset.duration)
119 self.name = dataset.name
123 """Parse a file with the collected data.
125 The data must be in 2 rows (x,y).
128 filename: Full path to the file.
131 f = open(filename, 'r')
133 metadata = Metadata()
134 data = [] # array of dataset
137 for num, line in enumerate(f):
140 if not line: continue
142 if not metadata.complete:
146 if re.match('[a-z_]', line):
149 if line.startswith('# StopWatch'): # Start of a new dataset
152 metadata.UpdateWith(dataset)
156 dataset = DataSet(line)
159 if line.startswith('#'):
162 # must be data at this stage
164 (time, value) = line.split(None, 1)
166 print 'skipping line %d: %s' % (num, line)
169 if dataset and not dataset.summary:
170 dataset.Add(float(time), float(value))
173 print 'Error parsing line %d' % num, sys.exc_info()[0]
176 if not metadata.complete:
177 print """Error missing metadata. Did you mount debugfs?
178 [adb shell mount -t debugfs none /sys/kernel/debug]"""
180 return (metadata, data)
183 def PlotIterations(metadata, data):
184 """Plot the duration of the ops against iteration.
186 If you are plotting data with widely different runtimes you probably want to
187 use PlotTimes instead.
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
196 metadata: For the graph's title.
197 data: pair of to be plotted.
200 gp = Gnuplot.Gnuplot(persist=1)
201 gp('set data style lines')
203 gp.xlabel('iterations')
204 gp.ylabel('duration in second')
205 gp.title(metadata.AsTitle())
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
215 d = Gnuplot.Data(x, dataset.data,
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])
223 gp.hardcopy('/tmp/%s-%s-%f.png' %
224 (metadata.name, metadata.kernel, metadata.duration),
228 def PlotTimes(metadata, data):
229 """Plot the duration of the ops against time elapsed.
232 metadata: For the graph's title.
233 data: pair of to be plotted.
236 gp = Gnuplot.Gnuplot(persist=1)
237 gp('set data style impulses')
241 gp.ylabel('duration in second')
242 gp.title(metadata.AsTitle())
247 x = numpy.array(dataset.time, dtype='float_')
248 if not dataset.name in styles:
249 styles[dataset.name] = line_style
251 d = Gnuplot.Data(x, dataset.data,
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])
259 gp.hardcopy('/tmp/%s-%s-%f.png' %
260 (metadata.name, metadata.kernel, metadata.duration),
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')
270 gp('set pointsize 2')
272 gp.xlabel('writer process')
273 gp.ylabel('duration in second')
274 gp.title(metadata.AsTitle())
277 x = numpy.array(dataset.time, dtype='int_')
278 d = Gnuplot.Data(x, dataset.data,
282 gp.hardcopy('/tmp/%s-%s-%f.png' %
283 (metadata.name, metadata.kernel, metadata.duration),
288 """Print this module's usage."""
290 To plot the result using the iter number of the x axis:
292 plot_sdcard.py -i /tmp/data.txt
294 To plot the result using time for the x axis:
296 plot_sdcard.py -t /tmp/data.txt
298 To plot the result from the profiler:
309 (optlist, args) = getopt.getopt(argv[1:],
310 'itp', ['iteration', 'time', 'profile'])
311 except getopt.GetoptError, err:
315 for flag, val in optlist:
316 if flag in ('-i', '--iteration'):
317 (metadata, data) = Parse(args[0])
318 PlotIterations(metadata, data)
320 elif flag in ('-t', '--time'):
321 (metadata, data) = Parse(args[0])
322 PlotTimes(metadata, data)
324 elif flag in ('-p', '--profile'):
330 if __name__ == '__main__':
331 if Gnuplot.__version__ != "1.8":
332 print "Gnuplot should be 1.8. See REAME file"