Blame btt/btt_plot.py

Packit Service 767812
#!/usr/libexec/platform-python
Packit c4abd9
#
Packit c4abd9
# btt_plot.py: Generate matplotlib plots for BTT generate data files
Packit c4abd9
#
Packit c4abd9
#  (C) Copyright 2009 Hewlett-Packard Development Company, L.P.
Packit c4abd9
#
Packit c4abd9
#  This program is free software; you can redistribute it and/or modify
Packit c4abd9
#  it under the terms of the GNU General Public License as published by
Packit c4abd9
#  the Free Software Foundation; either version 2 of the License, or
Packit c4abd9
#  (at your option) any later version.
Packit c4abd9
#
Packit c4abd9
#  This program is distributed in the hope that it will be useful,
Packit c4abd9
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit c4abd9
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit c4abd9
#  GNU General Public License for more details.
Packit c4abd9
#
Packit c4abd9
#  You should have received a copy of the GNU General Public License
Packit c4abd9
#  along with this program; if not, write to the Free Software
Packit c4abd9
#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
Packit c4abd9
#
Packit c4abd9
Packit c4abd9
"""
Packit c4abd9
btt_plot.py: Generate matplotlib plots for BTT generated data files
Packit c4abd9
Packit c4abd9
Files handled:
Packit c4abd9
  AQD	- Average Queue Depth		Running average of queue depths
Packit c4abd9
Packit c4abd9
  BNOS	- Block numbers accessed	Markers for each block
Packit c4abd9
Packit c4abd9
  Q2D	- Queue to Issue latencies	Running averages
Packit c4abd9
  D2C	- Issue to Complete latencies	Running averages
Packit c4abd9
  Q2C	- Queue to Complete latencies	Running averages
Packit c4abd9
Packit c4abd9
Usage:
Packit c4abd9
  btt_plot_aqd.py	equivalent to: btt_plot.py -t aqd	<type>=aqd
Packit c4abd9
  btt_plot_bnos.py	equivalent to: btt_plot.py -t bnos	<type>=bnos
Packit c4abd9
  btt_plot_q2d.py	equivalent to: btt_plot.py -t q2d	<type>=q2d
Packit c4abd9
  btt_plot_d2c.py	equivalent to: btt_plot.py -t d2c	<type>=d2c
Packit c4abd9
  btt_plot_q2c.py	equivalent to: btt_plot.py -t q2c	<type>=q2c
Packit c4abd9
Packit c4abd9
Arguments:
Packit c4abd9
  [ -A          | --generate-all   ] Default: False
Packit c4abd9
  [ -L          | --no-legend      ] Default: Legend table produced
Packit c4abd9
  [ -o <file>   | --output=<file>  ] Default: <type>.png
Packit c4abd9
  [ -T <string> | --title=<string> ] Default: Based upon <type>
Packit c4abd9
  [ -v          | --verbose        ] Default: False
Packit c4abd9
  <data-files...>
Packit c4abd9
Packit c4abd9
  The -A (--generate-all) argument is different: when this is specified,
Packit c4abd9
  an attempt is made to generate default plots for all 5 types (aqd, bnos,
Packit c4abd9
  q2d, d2c and q2c). It will find files with the appropriate suffix for
Packit c4abd9
  each type ('aqd.dat' for example). If such files are found, a plot for
Packit c4abd9
  that type will be made. The output file name will be the default for
Packit c4abd9
  each type. The -L (--no-legend) option will be obeyed for all plots,
Packit c4abd9
  but the -o (--output) and -T (--title) options will be ignored.
Packit c4abd9
"""
Packit c4abd9
Packit Service c53734
from __future__ import absolute_import
Packit Service c53734
from __future__ import print_function
Packit Service c53734
import six
Packit Service c53734
from six.moves import range
Packit c4abd9
__author__ = 'Alan D. Brunelle <alan.brunelle@hp.com>'
Packit c4abd9
Packit c4abd9
#------------------------------------------------------------------------------
Packit c4abd9
Packit c4abd9
import matplotlib
Packit c4abd9
matplotlib.use('Agg')
Packit c4abd9
import getopt, glob, os, sys
Packit c4abd9
import matplotlib.pyplot as plt
Packit c4abd9
Packit c4abd9
plot_size	= [10.9, 8.4]	# inches...
Packit c4abd9
Packit c4abd9
add_legend	= True
Packit c4abd9
generate_all	= False
Packit c4abd9
output_file	= None
Packit c4abd9
title_str	= None
Packit c4abd9
type		= None
Packit c4abd9
verbose		= False
Packit c4abd9
Packit c4abd9
types		= [ 'aqd', 'q2d', 'd2c', 'q2c', 'live', 'bnos' ]
Packit c4abd9
progs		= [ 'btt_plot_%s.py' % t for t in types ]
Packit c4abd9
Packit c4abd9
get_base 	= lambda file: file[file.find('_')+1:file.rfind('_')]
Packit c4abd9
Packit c4abd9
#------------------------------------------------------------------------------
Packit c4abd9
def fatal(msg):
Packit c4abd9
	"""Generate fatal error message and exit"""
Packit c4abd9
Packit Service c53734
	print('FATAL: %s' % msg, file=sys.stderr)
Packit c4abd9
	sys.exit(1)
Packit c4abd9
Packit c4abd9
#------------------------------------------------------------------------------
Packit c4abd9
def gen_legends(ax, legends):
Packit c4abd9
	leg = ax.legend(legends, 'best', shadow=True)
Packit c4abd9
	frame = leg.get_frame()
Packit c4abd9
	frame.set_facecolor('0.80')
Packit c4abd9
	for t in leg.get_texts():
Packit c4abd9
		t.set_fontsize('xx-small')
Packit c4abd9
Packit c4abd9
#----------------------------------------------------------------------
Packit c4abd9
def get_data(files):
Packit c4abd9
	"""Retrieve data from files provided.
Packit c4abd9
Packit c4abd9
	Returns a database containing:
Packit c4abd9
		'min_x', 'max_x' 	- Minimum and maximum X values found
Packit c4abd9
		'min_y', 'max_y' 	- Minimum and maximum Y values found
Packit c4abd9
		'x', 'y'		- X & Y value arrays
Packit c4abd9
		'ax', 'ay'		- Running average over X & Y --
Packit c4abd9
					  if > 10 values provided...
Packit c4abd9
	"""
Packit c4abd9
	#--------------------------------------------------------------
Packit c4abd9
	def check(mn, mx, v):
Packit c4abd9
		"""Returns new min, max, and float value for those passed in"""
Packit c4abd9
Packit c4abd9
		v = float(v)
Packit c4abd9
		if mn == None or v < mn: mn = v
Packit c4abd9
		if mx == None or v > mx: mx = v
Packit c4abd9
		return mn, mx, v
Packit c4abd9
Packit c4abd9
	#--------------------------------------------------------------
Packit c4abd9
	def avg(xs, ys):
Packit c4abd9
		"""Computes running average for Xs and Ys"""
Packit c4abd9
Packit c4abd9
		#------------------------------------------------------
Packit c4abd9
		def _avg(vals):
Packit c4abd9
			"""Computes average for array of values passed"""
Packit c4abd9
Packit c4abd9
			total = 0.0
Packit c4abd9
			for val in vals:
Packit c4abd9
				total += val
Packit c4abd9
			return total / len(vals)
Packit c4abd9
Packit c4abd9
		#------------------------------------------------------
Packit c4abd9
		if len(xs) < 1000:
Packit c4abd9
			return xs, ys
Packit c4abd9
Packit c4abd9
		axs = [xs[0]]
Packit c4abd9
		ays = [ys[0]]
Packit c4abd9
		_xs = [xs[0]]
Packit c4abd9
		_ys = [ys[0]]
Packit c4abd9
Packit c4abd9
		x_range = (xs[-1] - xs[0]) / 100
Packit c4abd9
		for idx in range(1, len(ys)):
Packit c4abd9
			if (xs[idx] - _xs[0]) > x_range:
Packit c4abd9
				axs.append(_avg(_xs))
Packit c4abd9
				ays.append(_avg(_ys))
Packit c4abd9
				del _xs, _ys
Packit c4abd9
Packit c4abd9
				_xs = [xs[idx]]
Packit c4abd9
				_ys = [ys[idx]]
Packit c4abd9
			else:
Packit c4abd9
				_xs.append(xs[idx])
Packit c4abd9
				_ys.append(ys[idx])
Packit c4abd9
Packit c4abd9
		if len(_xs) > 1:
Packit c4abd9
			axs.append(_avg(_xs))
Packit c4abd9
			ays.append(_avg(_ys))
Packit c4abd9
Packit c4abd9
		return axs, ays
Packit c4abd9
Packit c4abd9
	#--------------------------------------------------------------
Packit c4abd9
	global verbose
Packit c4abd9
Packit c4abd9
	db = {}
Packit c4abd9
	min_x = max_x = min_y = max_y = None
Packit c4abd9
	for file in files:
Packit c4abd9
		if not os.path.exists(file):
Packit c4abd9
			fatal('%s not found' % file)
Packit c4abd9
		elif verbose:
Packit Service c53734
			print('Processing %s' % file)
Packit c4abd9
Packit c4abd9
		xs = []
Packit c4abd9
		ys = []
Packit c4abd9
		for line in open(file, 'r'):
Packit c4abd9
			f = line.rstrip().split(None)
Packit c4abd9
			if line.find('#') == 0 or len(f) < 2:
Packit c4abd9
				continue
Packit c4abd9
			(min_x, max_x, x) = check(min_x, max_x, f[0])
Packit c4abd9
			(min_y, max_y, y) = check(min_y, max_y, f[1])
Packit c4abd9
			xs.append(x)
Packit c4abd9
			ys.append(y)
Packit c4abd9
Packit c4abd9
		db[file] = {'x':xs, 'y':ys}
Packit c4abd9
		if len(xs) > 10:
Packit c4abd9
			db[file]['ax'], db[file]['ay'] = avg(xs, ys)
Packit c4abd9
		else:
Packit c4abd9
			db[file]['ax'] = db[file]['ay'] = None
Packit c4abd9
Packit c4abd9
	db['min_x'] = min_x
Packit c4abd9
	db['max_x'] = max_x
Packit c4abd9
	db['min_y'] = min_y
Packit c4abd9
	db['max_y'] = max_y
Packit c4abd9
	return db
Packit c4abd9
Packit c4abd9
#----------------------------------------------------------------------
Packit c4abd9
def parse_args(args):
Packit c4abd9
	"""Parse command line arguments.
Packit c4abd9
Packit c4abd9
	Returns list of (data) files that need to be processed -- /unless/
Packit c4abd9
	the -A (--generate-all) option is passed, in which case superfluous
Packit c4abd9
	data files are ignored...
Packit c4abd9
	"""
Packit c4abd9
Packit c4abd9
	global add_legend, output_file, title_str, type, verbose
Packit c4abd9
	global generate_all
Packit c4abd9
Packit c4abd9
	prog = args[0][args[0].rfind('/')+1:]
Packit c4abd9
	if prog == 'btt_plot.py':
Packit c4abd9
		pass
Packit c4abd9
	elif not prog in progs:
Packit c4abd9
		fatal('%s not a valid command name' % prog)
Packit c4abd9
	else:
Packit c4abd9
		type = prog[prog.rfind('_')+1:prog.rfind('.py')]
Packit c4abd9
Packit c4abd9
	s_opts = 'ALo:t:T:v'
Packit c4abd9
	l_opts = [ 'generate-all', 'type', 'no-legend', 'output', 'title',
Packit c4abd9
		   'verbose' ]
Packit c4abd9
Packit c4abd9
	try:
Packit c4abd9
		(opts, args) = getopt.getopt(args[1:], s_opts, l_opts)
Packit Service c53734
	except getopt.error as msg:
Packit Service c53734
		print(msg, file=sys.stderr)
Packit c4abd9
		fatal(__doc__)
Packit c4abd9
Packit c4abd9
	for (o, a) in opts:
Packit c4abd9
		if o in ('-A', '--generate-all'):
Packit c4abd9
			generate_all = True
Packit c4abd9
		elif o in ('-L', '--no-legend'):
Packit c4abd9
			add_legend = False
Packit c4abd9
		elif o in ('-o', '--output'):
Packit c4abd9
			output_file = a
Packit c4abd9
		elif o in ('-t', '--type'):
Packit c4abd9
			if not a in types:
Packit c4abd9
				fatal('Type %s not supported' % a)
Packit c4abd9
			type = a
Packit c4abd9
		elif o in ('-T', '--title'):
Packit c4abd9
			title_str = a
Packit c4abd9
		elif o in ('-v', '--verbose'):
Packit c4abd9
			verbose = True
Packit c4abd9
Packit c4abd9
	if type == None and not generate_all:
Packit c4abd9
		fatal('Need type of data files to process - (-t <type>)')
Packit c4abd9
Packit c4abd9
	return args
Packit c4abd9
Packit c4abd9
#------------------------------------------------------------------------------
Packit c4abd9
def gen_title(fig, type, title_str):
Packit c4abd9
	"""Sets the title for the figure based upon the type /or/ user title"""
Packit c4abd9
Packit c4abd9
	if title_str != None:
Packit c4abd9
		pass
Packit c4abd9
	elif type == 'aqd':
Packit c4abd9
		title_str = 'Average Queue Depth'
Packit c4abd9
	elif type == 'bnos':
Packit c4abd9
		title_str = 'Block Numbers Accessed'
Packit c4abd9
	elif type == 'q2d':
Packit c4abd9
		title_str = 'Queue (Q) To Issue (D) Average Latencies'
Packit c4abd9
	elif type == 'd2c':
Packit c4abd9
		title_str = 'Issue (D) To Complete (C) Average Latencies'
Packit c4abd9
	elif type == 'q2c':
Packit c4abd9
		title_str = 'Queue (Q) To Complete (C) Average Latencies'
Packit c4abd9
Packit c4abd9
	title = fig.text(.5, .95, title_str, horizontalalignment='center')
Packit c4abd9
	title.set_fontsize('large')
Packit c4abd9
Packit c4abd9
#------------------------------------------------------------------------------
Packit c4abd9
def gen_labels(db, ax, type):
Packit c4abd9
	"""Generate X & Y 'axis'"""
Packit c4abd9
Packit c4abd9
	#----------------------------------------------------------------------
Packit c4abd9
	def gen_ylabel(ax, type):
Packit c4abd9
		"""Set the Y axis label based upon the type"""
Packit c4abd9
Packit c4abd9
		if type == 'aqd':
Packit c4abd9
			str = 'Number of Requests Queued'
Packit c4abd9
		elif type == 'bnos':
Packit c4abd9
			str = 'Block Number'
Packit c4abd9
		else:
Packit c4abd9
			str = 'Seconds'
Packit c4abd9
		ax.set_ylabel(str)
Packit c4abd9
Packit c4abd9
	#----------------------------------------------------------------------
Packit c4abd9
	xdelta = 0.1 * (db['max_x'] - db['min_x'])
Packit c4abd9
	ydelta = 0.1 * (db['max_y'] - db['min_y'])
Packit c4abd9
Packit c4abd9
	ax.set_xlim(db['min_x'] - xdelta, db['max_x'] + xdelta)
Packit c4abd9
	ax.set_ylim(db['min_y'] - ydelta, db['max_y'] + ydelta)
Packit c4abd9
	ax.set_xlabel('Runtime (seconds)')
Packit c4abd9
	ax.grid(True)
Packit c4abd9
	gen_ylabel(ax, type)
Packit c4abd9
Packit c4abd9
#------------------------------------------------------------------------------
Packit c4abd9
def generate_output(type, db):
Packit c4abd9
	"""Generate the output plot based upon the type and database"""
Packit c4abd9
Packit c4abd9
	#----------------------------------------------------------------------
Packit c4abd9
	def color(idx, style):
Packit c4abd9
		"""Returns a color/symbol type based upon the index passed."""
Packit c4abd9
Packit Service c53734
		colors = [ 'b', 'g', 'r', 'c', 'm', 'y', 'k' ]
Packit c4abd9
		l_styles = [ '-', ':', '--', '-.' ]
Packit c4abd9
		m_styles = [ 'o', '+', '.', ',', 's', 'v', 'x', '<', '>' ]
Packit c4abd9
Packit c4abd9
		color = colors[idx % len(colors)]
Packit c4abd9
		if style == 'line':
Packit Service c53734
			style = l_styles[int((idx / len(l_styles)) % len(l_styles))]
Packit c4abd9
		elif style == 'marker':
Packit Service c53734
			style = m_styles[int((idx / len(m_styles)) % len(m_styles))]
Packit c4abd9
Packit c4abd9
		return '%s%s' % (color, style)
Packit c4abd9
Packit c4abd9
	#----------------------------------------------------------------------
Packit c4abd9
	global add_legend, output_file, title_str, verbose
Packit c4abd9
Packit c4abd9
	if output_file != None:
Packit c4abd9
		ofile = output_file
Packit c4abd9
	else:
Packit c4abd9
		ofile = '%s.png' % type
Packit c4abd9
Packit c4abd9
	if verbose:
Packit Service c53734
		print('Generating plot into %s' % ofile)
Packit c4abd9
Packit c4abd9
	fig = plt.figure(figsize=plot_size)
Packit c4abd9
	ax = fig.add_subplot(111)
Packit c4abd9
Packit c4abd9
	gen_title(fig, type, title_str)
Packit c4abd9
	gen_labels(db, ax, type)
Packit c4abd9
Packit c4abd9
	idx = 0
Packit c4abd9
	if add_legend:
Packit c4abd9
		legends = []
Packit c4abd9
	else:
Packit c4abd9
		legends = None
Packit c4abd9
Packit c4abd9
	keys = []
Packit Service c53734
	for file in six.iterkeys(db):
Packit c4abd9
		if not file in ['min_x', 'max_x', 'min_y', 'max_y']:
Packit c4abd9
			keys.append(file)
Packit c4abd9
Packit c4abd9
	keys.sort()
Packit c4abd9
	for file in keys:
Packit c4abd9
		dat = db[file]
Packit c4abd9
		if type == 'bnos':
Packit c4abd9
			ax.plot(dat['x'], dat['y'], color(idx, 'marker'),
Packit c4abd9
				markersize=1)
Packit c4abd9
		elif dat['ax'] == None:
Packit c4abd9
			continue	# Don't add legend
Packit c4abd9
		else:
Packit c4abd9
			ax.plot(dat['ax'], dat['ay'], color(idx, 'line'),
Packit c4abd9
				linewidth=1.0)
Packit c4abd9
		if add_legend:
Packit c4abd9
			legends.append(get_base(file))
Packit c4abd9
		idx += 1
Packit c4abd9
Packit c4abd9
	if add_legend and len(legends) > 0:
Packit c4abd9
		gen_legends(ax, legends)
Packit c4abd9
	plt.savefig(ofile)
Packit c4abd9
Packit c4abd9
#------------------------------------------------------------------------------
Packit c4abd9
def get_files(type):
Packit c4abd9
	"""Returns the list of files for the -A option based upon type"""
Packit c4abd9
Packit c4abd9
	if type == 'bnos':
Packit c4abd9
		files = []
Packit c4abd9
		for fn in glob.glob('*c.dat'):
Packit c4abd9
			for t in [ 'q2q', 'd2d', 'q2c', 'd2c' ]:
Packit c4abd9
				if fn.find(t) >= 0:
Packit c4abd9
					break
Packit c4abd9
			else:
Packit c4abd9
				files.append(fn)
Packit c4abd9
	else:
Packit c4abd9
		files = glob.glob('*%s.dat' % type)
Packit c4abd9
	return files
Packit c4abd9
Packit c4abd9
#------------------------------------------------------------------------------
Packit c4abd9
def do_bnos(files):
Packit c4abd9
	for file in files:
Packit c4abd9
		base = get_base(file)
Packit c4abd9
		title_str = 'Block Numbers Accessed: %s' % base
Packit c4abd9
		output_file = 'bnos_%s.png' % base
Packit c4abd9
		generate_output(t, get_data([file]))
Packit c4abd9
Packit c4abd9
#------------------------------------------------------------------------------
Packit c4abd9
def do_live(files):
Packit c4abd9
	global plot_size
Packit c4abd9
Packit c4abd9
	#----------------------------------------------------------------------
Packit c4abd9
	def get_live_data(fn):
Packit c4abd9
		xs = []
Packit c4abd9
		ys = []
Packit c4abd9
		for line in open(fn, 'r'):
Packit c4abd9
			f = line.rstrip().split()
Packit c4abd9
			if f[0] != '#' and len(f) == 2:
Packit c4abd9
				xs.append(float(f[0]))
Packit c4abd9
				ys.append(float(f[1]))
Packit c4abd9
		return xs, ys
Packit c4abd9
Packit c4abd9
	#----------------------------------------------------------------------
Packit c4abd9
	def live_sort(a, b):
Packit c4abd9
		if a[0] == 'sys' and b[0] == 'sys':
Packit c4abd9
			return 0
Packit c4abd9
		elif a[0] == 'sys' or a[2][0] < b[2][0]:
Packit c4abd9
			return -1
Packit c4abd9
		elif b[0] == 'sys' or a[2][0] > b[2][0]:
Packit c4abd9
			return  1
Packit c4abd9
		else:
Packit c4abd9
			return  0
Packit c4abd9
Packit c4abd9
	#----------------------------------------------------------------------
Packit c4abd9
	def turn_off_ticks(ax):
Packit c4abd9
		for tick in ax.xaxis.get_major_ticks():
Packit c4abd9
			tick.tick1On = tick.tick2On = False
Packit c4abd9
		for tick in ax.yaxis.get_major_ticks():
Packit c4abd9
			tick.tick1On = tick.tick2On = False
Packit c4abd9
		for tick in ax.xaxis.get_minor_ticks():
Packit c4abd9
			tick.tick1On = tick.tick2On = False
Packit c4abd9
		for tick in ax.yaxis.get_minor_ticks():
Packit c4abd9
			tick.tick1On = tick.tick2On = False
Packit c4abd9
Packit c4abd9
	#----------------------------------------------------------------------
Packit c4abd9
	fig = plt.figure(figsize=plot_size)
Packit c4abd9
	ax = fig.add_subplot(111)
Packit c4abd9
Packit c4abd9
	db = []
Packit c4abd9
	for fn in files:
Packit c4abd9
		if not os.path.exists(fn):
Packit c4abd9
			continue
Packit c4abd9
		(xs, ys) = get_live_data(fn)
Packit c4abd9
		db.append([fn[:fn.find('_live.dat')], xs, ys])
Packit c4abd9
	db.sort(live_sort)
Packit c4abd9
Packit c4abd9
	for rec in db:
Packit c4abd9
		ax.plot(rec[1], rec[2])
Packit c4abd9
Packit c4abd9
	gen_title(fig, 'live', 'Active I/O Per Device')
Packit c4abd9
	ax.set_xlabel('Runtime (seconds)')
Packit c4abd9
	ax.set_ylabel('Device')
Packit c4abd9
	ax.grid(False)
Packit c4abd9
Packit c4abd9
	ax.set_xlim(-0.1, db[0][1][-1]+1)
Packit c4abd9
	ax.set_yticks([idx for idx in range(0, len(db))])
Packit c4abd9
	ax.yaxis.set_ticklabels([rec[0] for rec in db])
Packit c4abd9
	turn_off_ticks(ax)
Packit c4abd9
Packit c4abd9
	plt.savefig('live.png')
Packit c4abd9
	plt.savefig('live.eps')
Packit c4abd9
Packit c4abd9
#------------------------------------------------------------------------------
Packit c4abd9
if __name__ == '__main__':
Packit c4abd9
	files = parse_args(sys.argv)
Packit c4abd9
Packit c4abd9
	if generate_all:
Packit c4abd9
		output_file = title_str = type = None
Packit c4abd9
		for t in types:
Packit c4abd9
			files = get_files(t)
Packit c4abd9
			if len(files) == 0:
Packit c4abd9
				continue
Packit c4abd9
			elif t == 'bnos':
Packit c4abd9
				do_bnos(files)
Packit c4abd9
			elif t == 'live':
Packit c4abd9
				do_live(files)
Packit c4abd9
			else:
Packit c4abd9
				generate_output(t, get_data(files))
Packit c4abd9
				continue
Packit c4abd9
Packit c4abd9
	elif len(files) < 1:
Packit c4abd9
		fatal('Need data files to process')
Packit c4abd9
	else:
Packit c4abd9
		generate_output(type, get_data(files))
Packit c4abd9
	sys.exit(0)