|
| 1 | +# |
| 2 | +# hdr-plot.py v0.2.0 - A simple HdrHistogram plotting script. |
| 3 | +# Copyright © 2018 Bruno Bonacci - Distributed under the Apache License v 2.0 |
| 4 | +# |
| 5 | +# usage: hdr-plot.py [-h] [--output OUTPUT] [--title TITLE] [--nobox] files [files ...] |
| 6 | +# |
| 7 | +# A standalone plotting script for https://github.com/giltene/wrk2 and |
| 8 | +# https://github.com/HdrHistogram/HdrHistogram. |
| 9 | +# |
| 10 | +# This is just a quick and unsophisticated script to quickly plot the |
| 11 | +# HdrHistograms directly from the output of `wkr2` benchmarks. |
| 12 | + |
| 13 | +import argparse |
| 14 | +import re |
| 15 | +import pandas as pd |
| 16 | +import matplotlib.pyplot as plt |
| 17 | +import matplotlib.ticker as ticker |
| 18 | + |
| 19 | +regex = re.compile(r'\s+([0-9.]+)\s+([0-9.]+)\s+([0-9.]+)\s+([0-9.]+)') |
| 20 | +filename = re.compile(r'(.*/)?([^.]*)(\.\w+\d+)?') |
| 21 | + |
| 22 | + |
| 23 | +def parse_percentiles(file): |
| 24 | + lines = [line for line in open(file) if re.match(regex, line)] |
| 25 | + values = [re.findall(regex, line)[0] for line in lines] |
| 26 | + pctles = [(float(v[0]), float(v[1]), int(v[2]), float(v[3])) for v in values] |
| 27 | + percentiles = pd.DataFrame(pctles, columns=['Latency', 'Percentile', 'TotalCount', 'inv-pct']) |
| 28 | + return percentiles |
| 29 | + |
| 30 | + |
| 31 | +def parse_files(files): |
| 32 | + return [parse_percentiles(file) for file in files] |
| 33 | + |
| 34 | + |
| 35 | +def info_text(name, data): |
| 36 | + textstr = '%-18s\n------------------\n%-6s = %6.2f ms\n%-6s = %6.2f ms\n%-6s = %6.2f ms\n' % ( |
| 37 | + name, |
| 38 | + "min", data['Latency'].min(), |
| 39 | + "median", data[data["Percentile"] == 0.5]["Latency"], |
| 40 | + "max", data['Latency'].max()) |
| 41 | + return textstr |
| 42 | + |
| 43 | + |
| 44 | +def info_box(ax, text): |
| 45 | + props = dict(boxstyle='round', facecolor='lightcyan', alpha=0.5) |
| 46 | + |
| 47 | + # place a text box in upper left in axes coords |
| 48 | + ax.text(0.05, 0.95, text, transform=ax.transAxes, |
| 49 | + verticalalignment='top', bbox=props, fontname='monospace') |
| 50 | + |
| 51 | + |
| 52 | +def plot_summarybox(ax, percentiles, labels): |
| 53 | + # add info box to the side |
| 54 | + textstr = '\n'.join([info_text(labels[i], percentiles[i]) for i in range(len(labels))]) |
| 55 | + info_box(ax, textstr) |
| 56 | + |
| 57 | + |
| 58 | +def plot_percentiles(percentiles, labels): |
| 59 | + y_range = [0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50] |
| 60 | + x_range = [0.25, 0.5, 0.9, 0.99, 0.999, 0.9999, 0.99999, 0.999999] |
| 61 | + |
| 62 | + fig, ax = plt.subplots(figsize=(24, 16)) |
| 63 | + plt.ylim(0, 50) |
| 64 | + # plot values |
| 65 | + for data in percentiles: |
| 66 | + ax.plot(data['Percentile'], data['Latency']) |
| 67 | + |
| 68 | + # set axis and legend |
| 69 | + ax.grid() |
| 70 | + ax.set(xlabel='Percentile', |
| 71 | + ylabel='Latency (milliseconds)', |
| 72 | + title='Latency Percentiles (lower is better)') |
| 73 | + ax.set_xscale('logit') |
| 74 | + plt.yticks(y_range) |
| 75 | + plt.xticks(x_range) |
| 76 | + majors = ["25%", "50%", "90%", "99%", "99.9%", "99.99%", "99.999%", "99.9999%"] |
| 77 | + ax.xaxis.set_major_formatter(ticker.FixedFormatter(majors)) |
| 78 | + ax.xaxis.set_minor_formatter(ticker.NullFormatter()) |
| 79 | + plt.legend(bbox_to_anchor=(0., 1.02, 1., .102), |
| 80 | + loc=3, ncol=2, borderaxespad=0., |
| 81 | + labels=labels) |
| 82 | + |
| 83 | + return fig, ax |
| 84 | + |
| 85 | + |
| 86 | +def arg_parse(): |
| 87 | + parser = argparse.ArgumentParser(description='Plot HDRHistogram latencies.') |
| 88 | + parser.add_argument('files', nargs='+', help='list HDR files to plot') |
| 89 | + parser.add_argument('--output', default='latency.png', |
| 90 | + help='Output file name (default: latency.png)') |
| 91 | + parser.add_argument('--title', default='', help='The plot title.') |
| 92 | + parser.add_argument("--nobox", help="Do not plot summary box", |
| 93 | + action="store_true") |
| 94 | + args = parser.parse_args() |
| 95 | + return args |
| 96 | + |
| 97 | + |
| 98 | +def main(): |
| 99 | + # print command line arguments |
| 100 | + args = arg_parse() |
| 101 | + |
| 102 | + # load the data and create the plot |
| 103 | + pct_data = parse_files(args.files) |
| 104 | + labels = [re.findall(filename, file)[0][1] for file in args.files] |
| 105 | + # plotting data |
| 106 | + fig, ax = plot_percentiles(pct_data, labels) |
| 107 | + # plotting summary box |
| 108 | + if not args.nobox: |
| 109 | + plot_summarybox(ax, pct_data, labels) |
| 110 | + # add title |
| 111 | + plt.suptitle(args.title) |
| 112 | + # save image |
| 113 | + plt.savefig(args.output) |
| 114 | + print("Wrote: " + args.output) |
| 115 | + |
| 116 | + |
| 117 | +if __name__ == "__main__": |
| 118 | + main() |
0 commit comments