plot_sdcard.py revision 39c016f875b793296a121f41de5775b88f6fa1c9
1#!/usr/bin/python2.5
2#
3# Copyright 2009 Google Inc. All Rights Reserved.
4
5"""plot_sdcard: A module to plot the results of an sdcard perf test.
6
7Requires Gnuplot python v 1.8
8
9Typical usage:
10
11python
12>>> import plot_sdcard as p
13>>> (metadata, data) = p.parse('/tmp/data.txt')
14>>> p.plotIterations(metadata, data)
15>>> p.plotTimes(metadata, data)
16
17"""
18
19#TODO: provide a main so we can pipe the result from the run
20#TODO: more comments...
21
22import Gnuplot
23from numpy import *
24import sys
25import re
26from itertools import izip
27
28class DataSet(object):
29  def __init__(self, line):
30    res = re.search('# StopWatch ([\w]+) total/cumulative duration ([0-9.]+)\. Samples: ([0-9]+)', line)
31    self.time = []
32    self.data = []
33    self.name = res.group(1)
34    self.duration = float(res.group(2))
35    self.iteration = int(res.group(3))
36    print "Name: %s Duration: %f Iterations: %d" % (self.name, self.duration, self.iteration)
37    self.summary = re.match('([a-z_]+)_total', self.name)
38
39  def __repr__(self):
40    return str(zip(self.time, self.data))
41
42  def add(self, time, value):
43    self.time.append(time)
44    self.data.append(value)
45
46  def rescaleTo(self, length):
47    factor = len(self.data) / length
48
49    if factor > 1:
50      new_time = []
51      new_data = []
52      accum = 0.0
53      idx = 1
54      for t,d in izip(self.time, self.data):
55        accum += d
56        if idx % factor == 0:
57          new_time.append(t)
58          new_data.append(accum / factor)
59          accum = 0
60        idx += 1
61      self.time = new_time
62      self.data = new_data
63
64
65class Metadata(object):
66  def __init__(self):
67    self.kernel = ''
68    self.command_line = ''
69    self.sched = ''
70    self.name = ''
71    self.fadvise = ''
72    self.iterations = 0
73    self.duration = 0.0
74    self.complete = False
75
76  def parse(self, line):
77    if line.startswith('# Kernel:'):
78      self.kernel = re.search('Linux version ([0-9.]+-[0-9]+)', line).group(1)
79    elif line.startswith('# Command:'):
80      self.command_line = re.search('# Command: [/\w_]+ (.*)', line).group(1)
81      self.command_line = self.command_line.replace(' --', '-')
82      self.command_line = self.command_line.replace(' -d', '')
83      self.command_line = self.command_line.replace('--test=', '')
84    elif line.startswith('# Iterations'):
85      self.iterations = int(re.search('# Iterations: ([0-9]+)', line).group(1))
86    elif line.startswith('# Fadvise'):
87      self.fadvise = int(re.search('# Fadvise: ([\w]+)', line).group(1))
88    elif line.startswith("# Sched"):
89      self.sched = re.search('# Sched features: ([\w]+)', line).group(1)
90      self.complete = True
91
92  def asTitle(self):
93    return "%s-duration:%f\\n-%s\\n%s" % (self.kernel, self.duration, self.command_line, self.sched)
94
95  def updateWith(self, dataset):
96    self.duration = max(self.duration, dataset.duration)
97    self.name = dataset.name
98
99
100def plotIterations(metadata, data):
101  gp = Gnuplot.Gnuplot(persist = 1)
102  gp('set data style lines')
103  gp.clear()
104  gp.xlabel("iterations")
105  gp.ylabel("duration in second")
106  gp.title(metadata.asTitle())
107  styles = {}
108  line_style = 1
109
110  for dataset in data:
111    dataset.rescaleTo(metadata.iterations)
112    x = arange(len(dataset.data), dtype='int_')
113    if not dataset.name in styles:
114      styles[dataset.name] = line_style
115      line_style += 1
116      d = Gnuplot.Data(x, dataset.data,
117                       title=dataset.name,
118                       with_='lines ls %d' % styles[dataset.name])
119    else: # no need to repeat a title that exists already.
120      d = Gnuplot.Data(x, dataset.data,
121                       with_='lines ls %d' % styles[dataset.name])
122
123    gp.replot(d)
124  gp.hardcopy('/tmp/%s-%s-%f.png' % (metadata.name, metadata.kernel, metadata.duration), terminal='png')
125
126def plotTimes(metadata, data):
127  gp = Gnuplot.Gnuplot(persist = 1)
128  gp('set data style impulses')
129  gp('set xtics 1')
130  gp.clear()
131  gp.xlabel("seconds")
132  gp.ylabel("duration in second")
133  gp.title(metadata.asTitle())
134  styles = {}
135  line_style = 1
136
137  for dataset in data:
138    #dataset.rescaleTo(metadata.iterations)
139    x = array(dataset.time, dtype='float_')
140    if not dataset.name in styles:
141      styles[dataset.name] = line_style
142      line_style += 1
143      d = Gnuplot.Data(x, dataset.data,
144                       title=dataset.name,
145                       with_='impulses ls %d' % styles[dataset.name])
146    else: # no need to repeat a title that exists already.
147      d = Gnuplot.Data(x, dataset.data,
148                       with_='impulses ls %d' % styles[dataset.name])
149
150    gp.replot(d)
151  gp.hardcopy('/tmp/%s-%s-%f.png' % (metadata.name, metadata.kernel, metadata.duration), terminal='png')
152
153
154def parse(filename):
155  f = open(filename, 'r')
156
157  metadata = Metadata()
158  data = []  # array of dataset
159  dataset = None
160
161  for num, line in enumerate(f):
162    try:
163      line = line.strip()
164      if not line: continue
165
166      if not metadata.complete:
167        metadata.parse(line)
168        continue
169
170      if re.match('[a-z_]', line):
171        continue
172
173      if line.startswith('# StopWatch'): # Start of a new dataset
174        if dataset:
175          if dataset.summary:
176            metadata.updateWith(dataset)
177          else:
178            data.append(dataset)
179
180        dataset = DataSet(line)
181        continue
182
183      if line.startswith('#'):
184        continue
185
186      # must be data at this stage
187      try:
188        (time, value) = line.split(None, 1)
189      except ValueError:
190        print "skipping line %d: %s" % (num, line)
191        continue
192
193      if dataset and not dataset.summary:
194        dataset.add(float(time), float(value))
195
196    except Exception, e:
197      print "Error parsing line %d" % num, sys.exc_info()[0]
198      raise
199  data.append(dataset)
200  return (metadata, data)
201