source: benchmark/plot.py @ 08e0d65

Last change on this file since 08e0d65 was 8fca132, checked in by Thierry Delisle <tdelisle@…>, 2 years ago

Changed plots to use different markers and dotted lines for minimum

  • Property mode set to 100755
File size: 7.6 KB
Line 
1#!/usr/bin/python3
2"""
3Python Script to plot values obtained by the rmit.py script
4Runs a R.I.P.L.
5
6./plot.py
7-t trials
8-o option:values
9"""
10
11import argparse
12import itertools
13import json
14import math
15import numpy
16import os
17import re
18import statistics
19import sys
20import time
21
22import matplotlib
23import matplotlib.pyplot as plt
24from matplotlib.ticker import EngFormatter, ScalarFormatter
25
26def fmtDur( duration ):
27        if duration :
28                hours, rem = divmod(duration, 3600)
29                minutes, rem = divmod(rem, 60)
30                seconds, millis = divmod(rem, 1)
31                return "%2d:%02d.%03d" % (minutes, seconds, millis * 1000)
32        return " n/a"
33
34class Field:
35        def __init__(self, unit, _min, _log, _name=None, _factor=1.0):
36                self.unit = unit
37                self.min  = _min
38                self.log  = _log
39                self.name = _name
40                self.factor = _factor
41
42field_names = {
43        "ns per ops"            : Field('ns'    , 0, False),
44        "Number of processors"  : Field(''      , 1, "exact"),
45        "Ops per procs"         : Field('Ops'   , 0, False),
46        "Ops per threads"       : Field('Ops'   , 0, False),
47        "ns per ops/procs"      : Field(''      , 0, False, _name = "ns $\\times$ (Processor $/$ Total Ops)" ),
48        "Number of threads"     : Field(''      , 1, False),
49        "Total Operations(ops)" : Field('Ops'   , 0, False),
50        "Ops/sec/procs"         : Field('Ops'   , 0, False),
51        "Total blocks"          : Field('Blocks', 0, False),
52        "Ops per second"        : Field(''      , 0, False),
53        "Cycle size (# thrds)"  : Field('thrd'  , 1, False),
54        "Duration (ms)"         : Field('ms'    , 0, False),
55        "Target QPS"            : Field(''      , 0, False),
56        "Actual QPS"            : Field(''      , 0, False),
57        "Average Read Latency"  : Field('s'     , 0, False, _factor = 0.000001),
58        "Median Read Latency"   : Field('s'     , 0, True, _factor = 0.000001),
59        "Tail Read Latency"     : Field('s'     , 0, True, _factor = 0.000001),
60        "Average Update Latency": Field('s'     , 0, True, _factor = 0.000001),
61        "Median Update Latency" : Field('s'     , 0, True, _factor = 0.000001),
62        "Tail Update Latency"   : Field('s'     , 0, True, _factor = 0.000001),
63        "Update Ratio"          : Field('%'   , 0, False),
64        "Request Rate"          : Field('req/s' , 0, False),
65        "Data Rate"             : Field('b/s'   , 0, False, _factor = 1000 * 1000, _name = "Response Throughput"),
66        "Errors"                : Field('%'   , 0, False),
67}
68
69def plot(in_data, x, y, options, prefix):
70        fig, ax = plt.subplots()
71        colors  = itertools.cycle(['#006cb4','#0aa000','#ff6600','#8510a1','#0095e3','#fd8f00','#e30002','#8f00d6','#4b009a','#ffff00','#69df00','#fb0300','#b13f00'])
72        markers = itertools.cycle(['x', '+', '1', '2', '3', '4'])
73        series  = {} # scatter data for each individual data point
74        groups  = {} # data points for x value
75
76        print("Preparing Data")
77
78        for entry in in_data:
79                name = entry[0]
80                if options.filter and not name.startswith(options.filter):
81                        continue
82
83                if not name in series:
84                        series[name] = {'x':[], 'y':[]}
85
86                if not name in groups:
87                        groups[name] = {}
88
89                if x in entry[2] and y in entry[2]:
90                        xval = entry[2][x]
91                        yval = entry[2][y] * field_names[y].factor
92                        series[name]['x'].append(xval)
93                        series[name]['y'].append(yval)
94
95                        if not xval in groups[name]:
96                                groups[name][xval] = []
97
98                        groups[name][xval].append(yval)
99
100        print("Preparing Lines")
101
102        lines = {} # lines from groups with min, max, median, etc.
103        for name, data in groups.items():
104                if not name in lines:
105                        lines[name] = { 'x': [], 'min':[], 'max':[], 'med':[], 'avg':[] }
106
107                for xkey in sorted(data):
108                        ys = data[xkey]
109                        lines[name]['x']  .append(xkey)
110                        lines[name]['min'].append(min(ys))
111                        lines[name]['max'].append(max(ys))
112                        lines[name]['med'].append(statistics.median(ys))
113                        lines[name]['avg'].append(statistics.mean(ys))
114
115        print("Making Plots")
116
117        for name, data in sorted(series.items()):
118                _col = next(colors)
119                _mrk = next(markers)
120                plt.scatter(data['x'], data['y'], color=_col, label=name[len(prefix):], marker=_mrk)
121                plt.plot(lines[name]['x'], lines[name]['min'], ':', color=_col)
122                plt.plot(lines[name]['x'], lines[name]['max'], '--', color=_col)
123                plt.plot(lines[name]['x'], lines[name]['med'], '-', color=_col)
124
125        print("Calculating Extremums")
126
127        mx = max([max(s['x']) for s in series.values()])
128        my = max([max(s['y']) for s in series.values()])
129
130        print("Finishing Plots")
131
132        plt.ylabel(field_names[y].name if field_names[y].name else y)
133        # plt.xticks(range(1, math.ceil(mx) + 1))
134        plt.xlabel(field_names[x].name if field_names[x].name else x)
135        plt.grid(b = True)
136        ax.xaxis.set_major_formatter( EngFormatter(unit=field_names[x].unit) )
137        if options.logx:
138                ax.set_xscale('log')
139        elif field_names[x].log:
140                ax.set_xscale('log')
141                if field_names[x].log == "exact":
142                        xvals = set()
143                        for s in series.values():
144                                xvals |= set(s['x'])
145                        ax.set_xticks(sorted(xvals))
146                        ax.get_xaxis().set_major_formatter(ScalarFormatter())
147                        plt.xticks(rotation = 45)
148        else:
149                plt.xlim(field_names[x].min, mx + 0.25)
150
151        if options.logy:
152                ax.set_yscale('log')
153        elif field_names[y].log:
154                ax.set_yscale('log')
155        else:
156                plt.ylim(field_names[y].min, options.MaxY if options.MaxY else my*1.2)
157
158        ax.yaxis.set_major_formatter( EngFormatter(unit=field_names[y].unit) )
159
160        plt.legend(loc='upper left')
161
162        print("Results Ready")
163        start = time.time()
164        if options.out:
165                plt.savefig(options.out, bbox_inches='tight')
166        else:
167                plt.show()
168        end = time.time()
169        print("Took {}".format(fmtDur(end - start)))
170
171
172if __name__ == "__main__":
173        # ================================================================================
174        # parse command line arguments
175        parser = argparse.ArgumentParser(description='Python Script to draw R.M.I.T. results')
176        parser.add_argument('-f', '--file', nargs='?', type=argparse.FileType('r'), default=sys.stdin, help="Input file")
177        parser.add_argument('-o', '--out', nargs='?', type=str, default=None, help="Output file")
178        parser.add_argument('-y', nargs='?', type=str, default="", help="Which field to use as the Y axis")
179        parser.add_argument('-x', nargs='?', type=str, default="", help="Which field to use as the X axis")
180        parser.add_argument('--logx', action='store_true', help="if set, makes the x-axis logscale")
181        parser.add_argument('--logy', action='store_true', help="if set, makes the y-axis logscale")
182        parser.add_argument('--MaxY', nargs='?', type=int, help="maximum value of the y-axis")
183        parser.add_argument('--filter', nargs='?', type=str, default="", help="if not empty, only print series that start with specified filter")
184
185        options =  parser.parse_args()
186
187        # if not options.out:
188        #       matplotlib.use('SVG')
189
190        # ================================================================================
191        # load data
192        try :
193                data = json.load(options.file)
194        except :
195                print('ERROR: could not read input', file=sys.stderr)
196                parser.print_help(sys.stderr)
197                sys.exit(1)
198
199        # ================================================================================
200        # identify the keys
201
202        series = set()
203        fields = set()
204
205        for entry in data:
206                series.add(entry[0])
207                for label in entry[2].keys():
208                        fields.add(label)
209
210        # filter out the series if needed
211        if options.filter:
212                series = set(filter(lambda elem: elem.startswith(options.filter), series))
213
214        # find the common prefix on series for removal (only if no filter)
215        prefix = os.path.commonprefix(list(series))
216
217        if not options.out :
218                print(series)
219                print("fields: ", ' '.join(fields))
220
221        wantx = "Number of processors"
222        wanty = "ns per ops"
223
224        if options.x:
225                if options.x in field_names.keys():
226                        wantx = options.x
227                else:
228                        print("Could not find X key '{}', defaulting to '{}'".format(options.x, wantx))
229
230        if options.y:
231                if options.y in field_names.keys():
232                        wanty = options.y
233                else:
234                        print("Could not find Y key '{}', defaulting to '{}'".format(options.y, wanty))
235
236
237        plot(data, wantx, wanty, options, prefix)
Note: See TracBrowser for help on using the repository browser.