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)