github.com/letsencrypt/boulder@v0.20251208.0/test/load-generator/latency-charter.py (about) 1 #!/usr/bin/python 2 3 import matplotlib 4 import matplotlib.pyplot as plt 5 from matplotlib import gridspec 6 import numpy as np 7 import datetime 8 import json 9 import pandas 10 import argparse 11 import os 12 matplotlib.style.use('ggplot') 13 14 # sacrificial plot for single legend 15 matplotlib.rcParams['figure.figsize'] = 1, 1 16 randFig = plt.figure() 17 randAx = plt.subplot() 18 randAx.plot(0, 0, color='green', label='good', marker='+') 19 randAx.plot(0, 0, color='red', label='failed', marker='x') 20 randAx.plot(0, 0, color='black', label='sent', linestyle='--') 21 randAx.plot(0, 0, color='green', label='50th quantile') 22 randAx.plot(0, 0, color='orange', label='90th quantile') 23 randAx.plot(0, 0, color='red', label='99th quantile') 24 handles, labels = randAx.get_legend_handles_labels() 25 26 # big ol' plotting method 27 def plot_section(all_data, title, outputPath): 28 # group calls by the endpoint/method 29 actions = all_data.groupby('action') 30 h = len(actions.groups.keys()) 31 matplotlib.rcParams['figure.figsize'] = 20, 3 * h 32 33 fig = plt.figure() 34 fig.legend(handles, labels, ncol=6, fontsize=16, framealpha=0, loc='upper center') 35 if title is not None: 36 fig.suptitle(title, fontsize=20, y=0.93) 37 gs = gridspec.GridSpec(h, 3) 38 39 # figure out left and right datetime bounds 40 started = all_data['sent'].min() 41 stopped = all_data['finished'].max() 42 43 i = 0 44 # plot one row of charts for each endpoint/method combination 45 for section in actions.groups.keys(): 46 # setup the tree charts 47 ax = fig.add_subplot(gs[i, 0]) 48 ax.set_title(section) 49 ax.set_xlim(started, stopped) 50 ax2 = fig.add_subplot(gs[i, 2]) 51 ax2.set_xlim(started, stopped) 52 ax3 = fig.add_subplot(gs[i, 1]) 53 ax3.set_xlim(started, stopped) 54 55 # find the maximum y value and set it across all three charts 56 calls = actions.get_group(section) 57 tookMax = calls['took'].max() 58 ax.set_ylim(0, tookMax+tookMax*0.1) 59 ax2.set_ylim(0, tookMax+tookMax*0.1) 60 ax3.set_ylim(0, tookMax+tookMax*0.1) 61 62 groups = calls.groupby('type') 63 if groups.groups.get('error', False) is not False: 64 bad = groups.get_group('error') 65 ax.plot_date(bad['finished'], bad['took'], color='red', marker='x', label='error') 66 67 bad_rate = bad.set_index('finished') 68 bad_rate['rate'] = [0] * len(bad_rate.index) 69 bad_rate = bad_rate.resample('5S').count() 70 bad_rate['rate'] = bad_rate['rate'].divide(5) 71 rateMax = bad_rate['rate'].max() 72 ax2.plot_date(bad_rate.index, bad_rate['rate'], linestyle='-', marker='', color='red', label='error') 73 if groups.groups.get('good', False) is not False: 74 good = groups.get_group('good') 75 ax.plot_date(good['finished'], good['took'], color='green', marker='+', label='good') 76 77 good_rate = good.set_index('finished') 78 good_rate['rate'] = [0] * len(good_rate.index) 79 good_rate = good_rate.resample('5S').count() 80 good_rate['rate'] = good_rate['rate'].divide(5) 81 rateMax = good_rate['rate'].max() 82 ax2.plot_date(good_rate.index, good_rate['rate'], linestyle='-', marker='', color='green', label='good') 83 ax.set_ylabel('Latency (ms)') 84 85 # calculate the request rate 86 sent_rate = pandas.DataFrame(calls['sent']) 87 sent_rate = sent_rate.set_index('sent') 88 sent_rate['rate'] = [0] * len(sent_rate.index) 89 sent_rate = sent_rate.resample('5S').count() 90 sent_rate['rate'] = sent_rate['rate'].divide(5) 91 if sent_rate['rate'].max() > rateMax: 92 rateMax = sent_rate['rate'].max() 93 ax2.plot_date(sent_rate.index, sent_rate['rate'], linestyle='--', marker='', color='black', label='sent') 94 ax2.set_ylim(0, rateMax+rateMax*0.1) 95 ax2.set_ylabel('Rate (per second)') 96 97 # calculate and plot latency quantiles 98 calls = calls.set_index('finished') 99 calls = calls.sort_index() 100 quan = pandas.DataFrame(calls['took']) 101 for q, c in [[.5, 'green'], [.9, 'orange'], [.99, 'red']]: 102 quanN = quan.rolling(500, center=True).quantile(q) 103 ax3.plot(quanN['took'].index, quanN['took'], color=c) 104 105 ax3.set_ylabel('Latency quantiles (ms)') 106 107 i += 1 108 109 # format x axes 110 for ax in fig.axes: 111 matplotlib.pyplot.sca(ax) 112 plt.xticks(rotation=30, ha='right') 113 majorFormatter = matplotlib.dates.DateFormatter('%H:%M:%S') 114 ax.xaxis.set_major_formatter(majorFormatter) 115 116 # save image 117 gs.update(wspace=0.275, hspace=0.5) 118 fig.savefig(outputPath, bbox_inches='tight') 119 120 # and the main event 121 parser = argparse.ArgumentParser() 122 parser.add_argument('chartData', type=str, help='Path to file containing JSON chart output from load-generator') 123 parser.add_argument('--output', type=str, help='Path to save output to', default='latency-chart.png') 124 parser.add_argument('--title', type=str, help='Chart title') 125 args = parser.parse_args() 126 127 with open(args.chartData) as data_file: 128 stuff = [] 129 for l in data_file.readlines(): 130 stuff.append(json.loads(l)) 131 132 df = pandas.DataFrame(stuff) 133 df['finished'] = pandas.to_datetime(df['finished']).astype(datetime.datetime) 134 df['sent'] = pandas.to_datetime(df['sent']).astype(datetime.datetime) 135 df['took'] = df['took'].divide(1000000) 136 137 plot_section(df, args.title, args.output)