3 # btt_plot.py: Generate matplotlib plots for BTT generate data files
5 # (C) Copyright 2009 Hewlett-Packard Development Company, L.P.
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 2 of the License, or
10 # (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 btt_plot.py: Generate matplotlib plots for BTT generated data files
26 AQD - Average Queue Depth Running average of queue depths
28 BNOS - Block numbers accessed Markers for each block
30 Q2D - Queue to Issue latencies Running averages
31 D2C - Issue to Complete latencies Running averages
32 Q2C - Queue to Complete latencies Running averages
35 btt_plot_aqd.py equivalent to: btt_plot.py -t aqd <type>=aqd
36 btt_plot_bnos.py equivalent to: btt_plot.py -t bnos <type>=bnos
37 btt_plot_q2d.py equivalent to: btt_plot.py -t q2d <type>=q2d
38 btt_plot_d2c.py equivalent to: btt_plot.py -t d2c <type>=d2c
39 btt_plot_q2c.py equivalent to: btt_plot.py -t q2c <type>=q2c
42 [ -A | --generate-all ] Default: False
43 [ -L | --no-legend ] Default: Legend table produced
44 [ -o <file> | --output=<file> ] Default: <type>.png
45 [ -T <string> | --title=<string> ] Default: Based upon <type>
46 [ -v | --verbose ] Default: False
49 The -A (--generate-all) argument is different: when this is specified,
50 an attempt is made to generate default plots for all 5 types (aqd, bnos,
51 q2d, d2c and q2c). It will find files with the appropriate suffix for
52 each type ('aqd.dat' for example). If such files are found, a plot for
53 that type will be made. The output file name will be the default for
54 each type. The -L (--no-legend) option will be obeyed for all plots,
55 but the -o (--output) and -T (--title) options will be ignored.
58 __author__ = 'Alan D. Brunelle <alan.brunelle@hp.com>'
60 #------------------------------------------------------------------------------
64 import getopt, glob, os, sys
65 import matplotlib.pyplot as plt
67 plot_size = [10.9, 8.4] # inches...
76 types = [ 'aqd', 'q2d', 'd2c', 'q2c', 'live', 'bnos' ]
77 progs = [ 'btt_plot_%s.py' % t for t in types ]
79 get_base = lambda file: file[file.find('_')+1:file.rfind('_')]
81 #------------------------------------------------------------------------------
83 """Generate fatal error message and exit"""
85 print >>sys.stderr, 'FATAL: %s' % msg
88 #------------------------------------------------------------------------------
89 def gen_legends(ax, legends):
90 leg = ax.legend(legends, 'best', shadow=True)
91 frame = leg.get_frame()
92 frame.set_facecolor('0.80')
93 for t in leg.get_texts():
94 t.set_fontsize('xx-small')
96 #----------------------------------------------------------------------
98 """Retrieve data from files provided.
100 Returns a database containing:
101 'min_x', 'max_x' - Minimum and maximum X values found
102 'min_y', 'max_y' - Minimum and maximum Y values found
103 'x', 'y' - X & Y value arrays
104 'ax', 'ay' - Running average over X & Y --
105 if > 10 values provided...
107 #--------------------------------------------------------------
108 def check(mn, mx, v):
109 """Returns new min, max, and float value for those passed in"""
112 if mn == None or v < mn: mn = v
113 if mx == None or v > mx: mx = v
116 #--------------------------------------------------------------
118 """Computes running average for Xs and Ys"""
120 #------------------------------------------------------
122 """Computes average for array of values passed"""
127 return total / len(vals)
129 #------------------------------------------------------
138 x_range = (xs[-1] - xs[0]) / 100
139 for idx in range(1, len(ys)):
140 if (xs[idx] - _xs[0]) > x_range:
141 axs.append(_avg(_xs))
142 ays.append(_avg(_ys))
152 axs.append(_avg(_xs))
153 ays.append(_avg(_ys))
157 #--------------------------------------------------------------
161 min_x = max_x = min_y = max_y = None
163 if not os.path.exists(file):
164 fatal('%s not found' % file)
166 print 'Processing %s' % file
170 for line in open(file, 'r'):
171 f = line.rstrip().split(None)
172 if line.find('#') == 0 or len(f) < 2:
174 (min_x, max_x, x) = check(min_x, max_x, f[0])
175 (min_y, max_y, y) = check(min_y, max_y, f[1])
179 db[file] = {'x':xs, 'y':ys}
181 db[file]['ax'], db[file]['ay'] = avg(xs, ys)
183 db[file]['ax'] = db[file]['ay'] = None
191 #----------------------------------------------------------------------
192 def parse_args(args):
193 """Parse command line arguments.
195 Returns list of (data) files that need to be processed -- /unless/
196 the -A (--generate-all) option is passed, in which case superfluous
197 data files are ignored...
200 global add_legend, output_file, title_str, type, verbose
203 prog = args[0][args[0].rfind('/')+1:]
204 if prog == 'btt_plot.py':
206 elif not prog in progs:
207 fatal('%s not a valid command name' % prog)
209 type = prog[prog.rfind('_')+1:prog.rfind('.py')]
212 l_opts = [ 'generate-all', 'type', 'no-legend', 'output', 'title',
216 (opts, args) = getopt.getopt(args[1:], s_opts, l_opts)
217 except getopt.error, msg:
218 print >>sys.stderr, msg
222 if o in ('-A', '--generate-all'):
224 elif o in ('-L', '--no-legend'):
226 elif o in ('-o', '--output'):
228 elif o in ('-t', '--type'):
230 fatal('Type %s not supported' % a)
232 elif o in ('-T', '--title'):
234 elif o in ('-v', '--verbose'):
237 if type == None and not generate_all:
238 fatal('Need type of data files to process - (-t <type>)')
242 #------------------------------------------------------------------------------
243 def gen_title(fig, type, title_str):
244 """Sets the title for the figure based upon the type /or/ user title"""
246 if title_str != None:
249 title_str = 'Average Queue Depth'
251 title_str = 'Block Numbers Accessed'
253 title_str = 'Queue (Q) To Issue (D) Average Latencies'
255 title_str = 'Issue (D) To Complete (C) Average Latencies'
257 title_str = 'Queue (Q) To Complete (C) Average Latencies'
259 title = fig.text(.5, .95, title_str, horizontalalignment='center')
260 title.set_fontsize('large')
262 #------------------------------------------------------------------------------
263 def gen_labels(db, ax, type):
264 """Generate X & Y 'axis'"""
266 #----------------------------------------------------------------------
267 def gen_ylabel(ax, type):
268 """Set the Y axis label based upon the type"""
271 str = 'Number of Requests Queued'
278 #----------------------------------------------------------------------
279 xdelta = 0.1 * (db['max_x'] - db['min_x'])
280 ydelta = 0.1 * (db['max_y'] - db['min_y'])
282 ax.set_xlim(db['min_x'] - xdelta, db['max_x'] + xdelta)
283 ax.set_ylim(db['min_y'] - ydelta, db['max_y'] + ydelta)
284 ax.set_xlabel('Runtime (seconds)')
288 #------------------------------------------------------------------------------
289 def generate_output(type, db):
290 """Generate the output plot based upon the type and database"""
292 #----------------------------------------------------------------------
293 def color(idx, style):
294 """Returns a color/symbol type based upon the index passed."""
296 colors = [ 'b', 'g', 'r', 'c', 'm', 'y', 'k' ]
297 l_styles = [ '-', ':', '--', '-.' ]
298 m_styles = [ 'o', '+', '.', ',', 's', 'v', 'x', '<', '>' ]
300 color = colors[idx % len(colors)]
302 style = l_styles[(idx / len(l_styles)) % len(l_styles)]
303 elif style == 'marker':
304 style = m_styles[(idx / len(m_styles)) % len(m_styles)]
306 return '%s%s' % (color, style)
308 #----------------------------------------------------------------------
309 global add_legend, output_file, title_str, verbose
311 if output_file != None:
314 ofile = '%s.png' % type
317 print 'Generating plot into %s' % ofile
319 fig = plt.figure(figsize=plot_size)
320 ax = fig.add_subplot(111)
322 gen_title(fig, type, title_str)
323 gen_labels(db, ax, type)
332 for file in db.iterkeys():
333 if not file in ['min_x', 'max_x', 'min_y', 'max_y']:
340 ax.plot(dat['x'], dat['y'], color(idx, 'marker'),
342 elif dat['ax'] == None:
343 continue # Don't add legend
345 ax.plot(dat['ax'], dat['ay'], color(idx, 'line'),
348 legends.append(get_base(file))
351 if add_legend and len(legends) > 0:
352 gen_legends(ax, legends)
355 #------------------------------------------------------------------------------
357 """Returns the list of files for the -A option based upon type"""
361 for fn in glob.glob('*c.dat'):
362 for t in [ 'q2q', 'd2d', 'q2c', 'd2c' ]:
368 files = glob.glob('*%s.dat' % type)
371 #------------------------------------------------------------------------------
374 base = get_base(file)
375 title_str = 'Block Numbers Accessed: %s' % base
376 output_file = 'bnos_%s.png' % base
377 generate_output(t, get_data([file]))
379 #------------------------------------------------------------------------------
383 #----------------------------------------------------------------------
384 def get_live_data(fn):
387 for line in open(fn, 'r'):
388 f = line.rstrip().split()
389 if f[0] != '#' and len(f) == 2:
390 xs.append(float(f[0]))
391 ys.append(float(f[1]))
394 #----------------------------------------------------------------------
396 if a[0] == 'sys' and b[0] == 'sys':
398 elif a[0] == 'sys' or a[2][0] < b[2][0]:
400 elif b[0] == 'sys' or a[2][0] > b[2][0]:
405 #----------------------------------------------------------------------
406 def turn_off_ticks(ax):
407 for tick in ax.xaxis.get_major_ticks():
408 tick.tick1On = tick.tick2On = False
409 for tick in ax.yaxis.get_major_ticks():
410 tick.tick1On = tick.tick2On = False
411 for tick in ax.xaxis.get_minor_ticks():
412 tick.tick1On = tick.tick2On = False
413 for tick in ax.yaxis.get_minor_ticks():
414 tick.tick1On = tick.tick2On = False
416 #----------------------------------------------------------------------
417 fig = plt.figure(figsize=plot_size)
418 ax = fig.add_subplot(111)
422 if not os.path.exists(fn):
424 (xs, ys) = get_live_data(fn)
425 db.append([fn[:fn.find('_live.dat')], xs, ys])
429 ax.plot(rec[1], rec[2])
431 gen_title(fig, 'live', 'Active I/O Per Device')
432 ax.set_xlabel('Runtime (seconds)')
433 ax.set_ylabel('Device')
436 ax.set_xlim(-0.1, db[0][1][-1]+1)
437 ax.set_yticks([idx for idx in range(0, len(db))])
438 ax.yaxis.set_ticklabels([rec[0] for rec in db])
441 plt.savefig('live.png')
442 plt.savefig('live.eps')
444 #------------------------------------------------------------------------------
445 if __name__ == '__main__':
446 files = parse_args(sys.argv)
449 output_file = title_str = type = None
459 generate_output(t, get_data(files))
463 fatal('Need data files to process')
465 generate_output(type, get_data(files))