aboutsummaryrefslogtreecommitdiffstats
path: root/host/utils/latency/graph.py
diff options
context:
space:
mode:
Diffstat (limited to 'host/utils/latency/graph.py')
-rwxr-xr-xhost/utils/latency/graph.py376
1 files changed, 376 insertions, 0 deletions
diff --git a/host/utils/latency/graph.py b/host/utils/latency/graph.py
new file mode 100755
index 000000000..6aa2ba4e5
--- /dev/null
+++ b/host/utils/latency/graph.py
@@ -0,0 +1,376 @@
+#!/usr/bin/env python
+#
+# Copyright 2012 Ettus Research LLC
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+#
+
+import sys, re
+from optparse import OptionParser
+
+import matplotlib.pyplot as plt
+import matplotlib.font_manager
+import numpy as np
+
+from gnuradio.eng_option import eng_option
+
+_units = [
+ (3, "k"),
+ (6, "M"),
+ (9, "G")
+]
+
+
+def _format_rate(rate):
+ for (u1, s1), (u2, s2) in zip(_units, _units[1:]):
+ n = pow(10, u1)
+ if rate >= n and rate < pow(10, u2):
+ r = rate % n
+ if r > 0:
+ return str(1.0 * rate / n) + " " + s1
+ else:
+ return str(rate / n) + " " + s1
+ return str(rate) + " "
+
+
+def _sort(series, keys):
+ if len(keys) == 0:
+ return []
+ key = keys[0]
+ rev = False
+ if key[0] == '-':
+ key = key[1:]
+ rev = True
+ l = []
+ for s in series:
+ if s[key] not in l:
+ l += [s[key]]
+ l.sort()
+ if rev:
+ l.reverse()
+ return [(key, l)] + _sort(series, keys[1:])
+
+
+def _order(series, sort_list):
+ if len(sort_list) == 0:
+ return series
+ (sort_key, sort_key_list) = sort_list[0]
+ if len(sort_key_list) == 0:
+ return []
+ #print sort_key, sort_key_list
+ l = []
+ for s in series:
+ if s[sort_key] == sort_key_list[0]:
+ l += [s]
+ #print l
+ return _order(l, sort_list[1:]) + _order(series, [(sort_list[0][0], sort_list[0][1][1:])] + sort_list[1:])
+
+
+def get_option_parser():
+ usage = "%prog: [options]"
+ parser = OptionParser(option_class=eng_option, usage=usage)
+
+ parser.add_option("", "--id", type="string", help="device ID [default: %default]", default=None)
+ parser.add_option("", "--sort", type="string", help="sort order [default: %default]", default="rate -spb -spp")
+ parser.add_option("", "--output", type="string", help="output file [default: %default]", default=None)
+ parser.add_option("", "--output-type", type="string", help="output file type [default: %default]", default="pdf")
+ parser.add_option("", "--output-size", type="string", help="output file size [default: %default pixels]",
+ default="1600,900")
+ parser.add_option("", "--xrange", type="float", help="X range [default: %default]", default=None)
+ parser.add_option("", "--title", type="string", help="additional title [default: %default]", default=None)
+ parser.add_option("", "--legend", type="string", help="legend position [default: %default]", default="lower right")
+ parser.add_option("", "--diff", action="store_true", help="compare results instead of just plotting them", default=None)
+ return parser
+
+
+def get_sorted_series(args, options):
+ series = []
+
+ if len(args) > 0:
+ with open(args[0]) as f:
+ lines = f.readlines()
+ else:
+ lines = sys.stdin.readlines()
+ if lines is None or len(lines) == 0:
+ return
+
+ for line in lines:
+ line = line.strip()
+ if len(line) == 0:
+ continue
+ x = {'file': line}
+ idx2 = 0
+ idx = line.find("latency-stats")
+ if idx > 0:
+ x['prefix'] = line[0:idx]
+ idx = line.find(".id_")
+ if idx > -1:
+ idx += 4
+ idx2 = line.find("-", idx)
+ x['id'] = line[idx:idx2]
+ if options.id is None:
+ options.id = x['id']
+ elif options.id != x['id']:
+ print "Different IDs:", options.id, x['id']
+ idx = line.find("-rate_")
+ if idx > -1:
+ idx += 6
+ idx2 = line.find("-", idx)
+ x['rate'] = int(line[idx:idx2])
+ idx = line.find("-spb_")
+ if idx > -1:
+ idx += 5
+ idx2 = line.find("-", idx)
+ x['spb'] = int(line[idx:idx2])
+ idx = line.find("-spp_")
+ if idx > -1:
+ idx += 5
+ #idx2 = line.find(".", idx)
+ idx2 = re.search("\D", line[idx:])
+ if idx2:
+ idx2 = idx + idx2.start()
+ else:
+ idx2 = -1
+ x['spp'] = int(line[idx:idx2])
+ idx = line.rfind(".")
+ if idx > -1 and idx >= idx2:
+ idx2 = re.search("\d", line[::-1][len(line) - idx:])
+ if idx2 and (idx2.start() > 0):
+ idx2 = idx2.start()
+ x['suffix'] = line[::-1][len(line) - idx:][0:idx2][::-1]
+ print x
+ series += [x]
+
+ sort_keys = options.sort.split()
+ print sort_keys
+ sorted_key_list = _sort(series, sort_keys)
+ print sorted_key_list
+ series = _order(series, sorted_key_list)
+
+ return series
+
+
+def main():
+ # Create object with all valid options
+ parser = get_option_parser()
+
+ # Read in given command line options and arguments
+ (options, args) = parser.parse_args()
+
+
+ # series contains path and attributes for all data sets given by args.
+ series = get_sorted_series(args, options)
+
+ # Read in actual data sets from file
+ data = read_series_data(series)
+
+ if options.diff:
+ data = calculate_data_diff(data)
+
+
+ # Get all the wanted properties for this plot
+ plt_props = get_plt_props(options)
+ print plt_props
+
+ mpl_plot(data, plt_props)
+
+ return 0
+
+
+def read_series_data(series):
+ result = []
+ for s in series:
+ data = {}
+ [data_x, data_y] = np.loadtxt(s['file'], delimiter=" ", unpack=True)
+ data['x'] = data_x
+ data['y'] = data_y
+ data['metadata'] = s
+ result.append(data)
+ return result
+
+
+def find_values(data, key):
+ result = []
+ for d in data:
+ val = d['metadata'][key]
+ if not val in result:
+ result.append(val)
+ return result
+
+
+def find_match(data, key, val):
+ result = []
+ for d in data:
+ meta = d['metadata']
+ if meta[key] == val:
+ result.append(d)
+ return result
+
+def get_data_diff(data):
+ if not data:
+ return data # just return. User didn't input any data.
+ if len(data) < 2:
+ return data[0] # Single data set. Can't calculate a diff.
+
+ print "diff %d: rate %s, spb %s, spp %s" % (len(data), data[0]['metadata']['rate'], data[0]['metadata']['spb'], data[0]['metadata']['spp'])
+
+ data = align_data(data)
+
+ min_len = len(data[0]['x'])
+ for d in data:
+ min_len = min(min_len, len(d['x']))
+
+ metadiff = ""
+ for d in data:
+ m = d['metadata']['prefix']
+ for r in "/._":
+ m = m.replace(r, "")
+ metadiff += m + "-"
+
+ xd = data[0]['x'][0:min_len]
+ yd = data[0]['y'][0:min_len]
+ meta = data[0]['metadata']
+ meta['diff'] = metadiff
+ other = data[1:]
+ for d in other:
+ y = d['y']
+ for i in range(len(yd)):
+ yd[i] -= y[i]
+
+ result = {}
+ result['x'] = xd
+ result['y'] = yd
+ result['metadata'] = meta
+ return result
+
+def align_data(data):
+ x_start = 0
+ for d in data:
+ x_start = max(x_start, d['x'][0])
+
+ for i in range(len(data)):
+ s = np.where(data[i]['x'] == x_start)[0]
+ data[i]['x'] = data[i]['x'][s:]
+ data[i]['y'] = data[i]['y'][s:]
+
+ return data
+
+
+def calculate_data_diff(data):
+ spps = find_values(data, "spp")
+ spbs = find_values(data, "spb")
+ rates = find_values(data, "rate")
+ print spps, "\t", spbs, "\t", rates
+ result = []
+ for rate in rates:
+ rd = find_match(data, "rate", rate)
+ for spb in spbs:
+ bd = find_match(rd, "spb", spb)
+ for spp in spps:
+ pd = find_match(bd, "spp", spp)
+ if len(pd) > 0:
+ result.append(get_data_diff(pd))
+
+ return result
+
+
+def get_plt_props(options):
+ plt_props = {}
+ plt_out = None
+ if options.output is not None:
+ try:
+ idx = options.output_size.find(",")
+ x = int(options.output_size[0:idx])
+ y = int(options.output_size[idx + 1:])
+ plt_out = {'name': options.output,
+ 'type': options.output_type,
+ 'size': [x, y]}
+ except:
+ plt_out = None
+
+ plt_props['output'] = plt_out
+
+ plt_title = "Latency (" + options.id + ")"
+ if options.title is not None and len(options.title) > 0:
+ plt_title += " - " + options.title
+ plt_props['title'] = plt_title
+
+ plt_props['xlabel'] = "Latency (us)"
+ plt_props['ylabel'] = "Normalised success of on-time burst transmission"
+
+ plt_legend_loc = None
+ if options.legend is not None:
+ plt_legend_loc = options.legend
+ plt_props['legend'] = plt_legend_loc
+
+ plt_xrange = None
+ if options.xrange is not None:
+ plt_xrange = [0, options.xrange]
+ plt_props['xrange'] = plt_xrange
+ return plt_props
+
+
+def mpl_plot(data, props):
+ plt_out = props['output']
+ plt_title = props['title']
+ plt_xlabel = props['xlabel']
+ plt_ylabel = props['ylabel']
+ plt_legend_loc = props['legend']
+ plt_xrange = props['xrange']
+
+ markers = ['.', ',', 'o', 'v', '^', '<', '>', '1', '2', '3', '4', '8',
+ 's', 'p', '*', 'h', 'H', '+', 'D', 'd', '|', '_']
+ colors = ['b', 'g', 'r', 'c', 'm', 'y', 'k', 'w']
+ midx = 0
+
+ # plot available data.
+ mylegend = []
+ for d in data:
+ mylegend.append(get_legend_str(d['metadata']))
+ plt.plot(d['x'], d['y'], marker=markers[midx], markerfacecolor=None)
+ midx = (midx + 1) % len(markers)
+
+ # Set all plot properties
+ plt.title(plt_title)
+ plt.xlabel(plt_xlabel)
+ plt.ylabel(plt_ylabel)
+ plt.grid()
+ fontP = matplotlib.font_manager.FontProperties()
+ fontP.set_size('x-small')
+ plt.legend(mylegend, loc=plt_legend_loc, prop=fontP, ncol=2)
+ if plt_xrange is not None:
+ plt.xlim(plt_xrange)
+
+ # Save plot to file, if option is given.
+ if plt_out is not None:
+ fig = plt.gcf() # get current figure
+ dpi = 100.0 # Could be any value. It exists to convert the input in pixels to inches/dpi.
+ figsize = (plt_out['size'][0] / dpi, plt_out['size'][1] / dpi) # calculate figsize in inches
+ fig.set_size_inches(figsize)
+ name = plt_out['name'] + "." + plt_out['type']
+ plt.savefig(name, dpi=dpi, bbox_inches='tight')
+
+ plt.show()
+
+
+def get_legend_str(meta):
+ lt = ""
+ if meta['diff']:
+ lt += meta['diff'] + " "
+ lt += "%ssps, SPB %d, SPP %d" % (_format_rate(meta['rate']), meta['spb'], meta['spp'])
+ return lt
+
+
+if __name__ == '__main__':
+ main()