]> rtime.felk.cvut.cz Git - can-benchmark.git/blob - continuous/www/wvperf2html.py
Update graph generator to actually work
[can-benchmark.git] / continuous / www / wvperf2html.py
1 #!/usr/bin/env python
2 #
3 # WvTest:
4 #   Copyright (C) 2012, 2014 Michal Sojka <sojka@os.inf.tu-dresden.de>
5 #       Licensed under the GNU Library General Public License, version 2.
6 #       See the included file named LICENSE for license information.
7 #
8 # This script converts a sequence of wvtest protocol outputs with
9 # results of performance measurements to interactive graphs (HTML +
10 # JavaScript). An example can be seen at
11 # http://os.inf.tu-dresden.de/~sojka/nul/performance.html.
12
13 from __future__ import print_function
14 import sys
15 import re
16 import os
17 import os.path
18 import string
19 import time
20 from datetime import timedelta,datetime
21 import numpy as np
22 import json
23
24
25 re_prefix = "\([0-9]+\) (?:#   )?"
26 re_date = re.compile('^! Date: (.*?) ok')
27 re_testing = re.compile('^('+re_prefix+')?\s*Testing "(.*)" in (.*):\s*$')
28 re_repo = re.compile('^! Repo (?P<url>[^ ]*) (?P<desc>[^ ]*) (?P<hash>[0-9a-f]*) ok')
29 re_assertion = re.compile('^('+re_prefix+')?!\s*(.*?)\s+(\S+)\s*$')
30 re_perf =  re.compile('^('+re_prefix+')?!\s*(.*?)\s+PERF:\s*(.*?)\s+(\S+)\s*$')
31 re_perfaxis = re.compile('axis="([^"]+)"')
32
33 def dateConv(date):
34     return int(time.mktime(date.timetuple()))*1000
35
36
37 class Axis:
38     def __init__(self, name=None, units=None):
39         self.name = name
40         self.units = units
41         self.num = None
42     def getLabel(self):
43         if self.units and self.name:
44             return "%s [%s]" % (self.name, self.units)
45         elif self.units:
46             return self.units
47         else:
48             return self.name
49     def __repr__(self): return "Axis(name=%s units=%s, id=%s)" % (self.name, self.units, hex(id(self)))
50
51 class Column:
52     def __init__(self, name, units, axis):
53         self.name = name
54         self.units = units
55         self.axis = axis
56     def __repr__(self): return "Column(name=%s units=%s axis=%s)" % (self.name, self.units, repr(self.axis))
57
58 class Row(dict):
59     def __init__(self, graph, date):
60         self.graph = graph
61         self.date = date
62     def __getitem__(self, column):
63         try:
64             return dict.__getitem__(self, column)
65         except KeyError:
66             return None
67     def getDate(self):
68         return dateConv(self.date)
69
70 class Graph:
71     def __init__(self, id, title):
72         self.columns = {}
73         self.columns_ordered = []
74         self.id = id
75         self.title = title
76         self.rows = []
77         self.date2row = {}
78         self.axes = {}
79         self.axes_ordered = []
80         self.dataname = title.translate(string.maketrans(" /", "--"), "\"':,+()").lower()+".json"
81
82     def __getitem__(self, date):
83         try:
84             rownum = self.date2row[date]
85         except KeyError:
86             rownum = len(self.rows)
87             self.date2row[date] = rownum
88         try:
89             return self.rows[rownum]
90         except IndexError:
91             self.rows[rownum:rownum] = [Row(self, date)]
92             return self.rows[rownum]
93
94     def addValue(self, date, col, val, units):
95         if col == "gw_latency" and val == 0:
96             val = None
97         row = self[date]
98         row[col] = val
99         if col not in self.columns:
100             axis=self.getAxis(units) or self.addAxis(units, Axis(units=units))
101             column = Column(col, units, axis)
102             self.columns[col] = column
103             self.columns_ordered.append(column)
104         else:
105             column = self.columns[col]
106             self.columns_ordered.remove(column)
107             self.columns_ordered.append(column)
108         self.columns[col].units=units
109         self.columns[col].axis.units=units
110
111     def addAxis(self, key, axis):
112         self.axes[key] = axis
113         return axis
114
115     def setAxis(self, col, key):
116         self.columns[col].axis = self.getAxis(key) or self.addAxis(key, Axis(name=key))
117
118     def getAxis(self, key):
119         if key not in self.axes: return None
120         return self.axes[key]
121
122     def findRanges(self):
123         for axis in list(self.axes.values()):
124             cols = [col for col in list(self.columns.values()) if col.axis == axis]
125             values = []
126             for col in cols:
127                 allvalues = np.array([row[col.name] for row in self.rows if row[col.name] != None], np.float64)
128                 lastmonth = allvalues[-30:]
129                 values.extend(lastmonth);
130             if len(values) > 0:
131                 axis.minrange = np.mean(values)/10.0
132
133     def fixupAxisNumbers(self):
134         # Sort axes according to the columns and number them
135         num = 0
136         for column in self.columns_ordered:
137             axis = column.axis
138             if axis not in self.axes_ordered:
139                 self.axes_ordered.insert(0, axis)
140                 axis.num = num
141                 num += 1
142         num = 0
143         for axis in self.axes_ordered:
144             axis.num = num
145             num += 1
146
147     def options_json(self):
148         options = {
149             'rangeSelector': {
150                 'selected': 5 # All
151             },
152             'title': {
153                 'text': self.title
154             },
155             'legend': {
156                 'enabled': True,
157                 'floating': False,
158                 'verticalAlign': "top",
159                 'x': 100,
160                 'y': 60,
161             },
162             'tooltip': {
163                 'formatter': "FUN(tooltip_formatter)END",
164                 'style': {
165                                     'whiteSpace': 'normal',
166                                     'width': '400px',
167                     },
168             },
169             'plotOptions': {
170                 'series': {
171                     'events': {
172                         'click': "FUN(series_onclick)END",
173                     },
174                     'dataGrouping' : {
175                         'enabled' : False,
176                     },
177                 }
178             },
179             'xAxis': {
180                 'ordinal' : False,
181                 },
182             'yAxis': [{
183                     'lineWidth': 1,
184                     'labels': { 'align': 'right',
185                         'x': -3 },
186                     'title': { 'text': axis.getLabel() },
187                     'minRange': axis.minrange,
188                     } for axis in self.axes_ordered],
189             'series': [{ 'name': '%s [%s]' % (col.name, col.units),
190                  'yAxis': col.axis.num }
191                 for col in self.columns_ordered]
192             }
193         return json.dumps(options, indent=True).replace('"FUN(', '').replace(')END"', '')
194
195     def getData(self):
196         data = [[[row.getDate(), row[col.name]] for row in sorted(self.rows, cmp, lambda r: r.date)] for col in self.columns_ordered]
197         return json.dumps(data).replace('], [', '],\n[')
198
199
200 class Graphs(dict):
201     pass
202
203 graphs = Graphs()
204 date2commit = {}
205 commit2msg = {}
206
207 date = datetime.now()
208
209 for line in sys.stdin:
210     line = line.rstrip()
211
212     match = re_date.match(line)
213     if (match):
214         dstr = match.group(1)
215         words = dstr.split()
216
217         date = datetime.strptime(string.join(words[0:2]), "%Y-%m-%d %H:%M:%S")
218         if (len(words) > 2):
219             zone_hours = int(words[2])/100
220             date -= timedelta(hours = zone_hours)
221         continue
222
223     match = re_repo.match(line)
224     if (match):
225         url = match.group('url')
226         desc = match.group('desc')
227         hash = match.group('hash')
228
229         date2commit[date] = hash
230         commit2msg[hash] = desc
231
232     match = re_testing.match(line)
233     if match:
234         what = match.group(2)
235         where = match.group(3)
236         (basename, ext) = os.path.splitext(os.path.basename(where))
237
238         if what != "all": title = what
239         else: title = basename
240         try:
241             graph = graphs[basename]
242         except KeyError:
243             graph = Graph(basename, title)
244             graphs[basename] = graph
245         continue
246
247     match = re_perf.match(line)
248     if match:
249         perfstr = match.group(3)
250         perf = perfstr.split()
251         col = perf[0]
252         try:
253             val = float(perf[1])
254         except ValueError:
255             val = None
256         try:
257             units = perf[2]
258             if '=' in units: units = None
259         except:
260             units = None
261         if match.group(4) != "ok":
262             val=None
263
264         graph.addValue(date, col, val, units)
265
266         match = re_perfaxis.search(perfstr)
267         if match:
268             graph.setAxis(col, match.group(1));
269
270 graphs = [g for g in list(graphs.values()) if len(g.columns)]
271 graphs = sorted(graphs, key=lambda g: g.title.lower())
272
273 for g in graphs:
274     g.findRanges()
275     g.fixupAxisNumbers()
276
277 print("""
278 <!DOCTYPE HTML>
279 <html>
280   <head>
281   <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
282   <title>NUL Performance Plots</title>
283
284   <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script>
285   <script type="text/javascript">
286     function tooltip_formatter() {
287       var s = '<b>'+ Highcharts.dateFormat('%a, %d %b %Y %H:%M:%S', this.x) +'</b><br/>';
288       s += commit2msg[date2commit[this.x]];
289       $.each(this.points, function(i, point) {
290         s += '<br/><span style="color:'+ point.series.color+';">'+ point.series.name +'</span>: '+point.y;
291       });
292       return s;
293     }
294     function series_onclick(event) {
295       var prevpoint = null;
296       for (var i in this.data) {
297         if (event.point == this.data[i]) {
298           if (i > 0) prevpoint = this.data[i-1];
299           break;
300         }
301       }
302       if (prevpoint && date2commit[prevpoint.x] != date2commit[event.point.x])
303           window.location = "https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/log/?qt=range&q="+date2commit[prevpoint.x]+'..'+date2commit[event.point.x];
304       else
305           window.location = "https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/log/?id="+date2commit[event.point.x];
306     }""")
307
308 def make_int_keys(json):
309     r = re.compile('"(\d+)"(.*)')
310     s = ''
311     for match in r.finditer(json):
312         s += match.expand('\\1\\2')
313     return s
314
315 print("var date2commit = {%s};" % ",\n".join(["%d: '%s'" % (dateConv(k), date2commit[k]) for k in sorted(date2commit.keys())]))
316 print("var commit2msg = %s;" % json.dumps(commit2msg, indent=True))
317 # for d in sorted(date2commit.keys()):
318 #     v = commits[d];
319 #     print('\t%d: { msg: "%s", hash: "%s" },' % (1000*time.mktime(d), v[0].replace('"', '\\"'), str(v[1]).replace('"', '\\"')))
320 print("""
321         </script>
322         <script type="text/javascript" src="js/highstock.js"></script>
323     </head>
324
325     <body>
326         <h1>Linux CAN subsystem performance plots</h1>
327     <p>The graphs below show performance numbers from various
328     CAN bus related benchmarks run on different Linux kernel versions.</p>
329     <p>Table of content:</p>
330         <ul>
331 """)
332 for graph in graphs:
333     print("     <li><a href='#%s'>%s</a></li>" % (graph.id, graph.title))
334 print("    </ul>")
335 for graph in graphs:
336     print("""
337     <h2><a name='%(id)s'>%(title)s</a></h2>
338         <div id="%(id)s" style="height: 400px"></div>
339     <script>
340 $.getJSON('%(dataname)s', function(data) {
341     var options = %(options)s
342     for (var i=0; i < data.length; i++)
343         options['series'][i]['data'] = data[i];
344     $("#%(id)s").highcharts("StockChart", options);
345 });
346 </script>
347 """ % {'id': graph.id,
348        'title': graph.title,
349        'dataname': graph.dataname,
350        'options': graph.options_json()})
351     print(graph.getData(), file=open(graph.dataname, 'w'))
352 print("""
353     </body>
354 </html>
355 """)
356
357 # Local Variables:
358 # compile-command: "make perf"
359 # End: