]> rtime.felk.cvut.cz Git - can-benchmark.git/blob - continuous/www/wvperf2html.py
ddd83e972550b48e23d05bb3151febd01436f15a
[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             'chart': {
150                 'borderWidth': 1,
151                 },
152             'rangeSelector': {
153                 'selected': 5 # All
154             },
155             'title': {
156                 'text': self.title
157             },
158             'legend': {
159                 'enabled': True,
160                 'floating': False,
161                 'verticalAlign': "top",
162                 'x': 100,
163                 'y': 60,
164             },
165             'tooltip': {
166                 'formatter': "FUN(tooltip_formatter)END",
167                 'style': {
168                                     'whiteSpace': 'normal',
169                                     'width': '400px',
170                     },
171             },
172             'plotOptions': {
173                 'series': {
174                     'events': {
175                         'click': "FUN(series_onclick)END",
176                     },
177                     'dataGrouping' : {
178                         'enabled' : False,
179                     },
180                 }
181             },
182             'xAxis': {
183                 'ordinal' : False,
184                 },
185             'yAxis': [{
186                     'lineWidth': 1,
187                     'labels': { 'align': 'right',
188                         'x': -3 },
189                     'title': { 'text': axis.getLabel() },
190                     'minRange': axis.minrange,
191                     'tickPixelInterval': 40,
192                     } for axis in self.axes_ordered],
193             'series': [{ 'name': '%s [%s]' % (col.name, col.units),
194                  'yAxis': col.axis.num }
195                 for col in self.columns_ordered]
196             }
197         return json.dumps(options, indent=True).replace('"FUN(', '').replace(')END"', '')
198
199     def getData(self):
200         data = [[[row.getDate(), row[col.name]] for row in sorted(self.rows, cmp, lambda r: r.date)] for col in self.columns_ordered]
201         return json.dumps(data).replace('], [', '],\n[')
202
203
204 class Graphs(dict):
205     pass
206
207 graphs = Graphs()
208 date2commit = {}
209 commit2msg = {}
210
211 date = datetime.now()
212
213 for line in sys.stdin:
214     line = line.rstrip()
215
216     match = re_date.match(line)
217     if (match):
218         dstr = match.group(1)
219         words = dstr.split()
220
221         date = datetime.strptime(string.join(words[0:2]), "%Y-%m-%d %H:%M:%S")
222         if (len(words) > 2):
223             zone_hours = int(words[2])/100
224             date -= timedelta(hours = zone_hours)
225         continue
226
227     match = re_repo.match(line)
228     if (match):
229         url = match.group('url')
230         desc = match.group('desc')
231         hash = match.group('hash')
232
233         date2commit[date] = hash
234         commit2msg[hash] = desc
235
236     match = re_testing.match(line)
237     if match:
238         what = match.group(2)
239         where = match.group(3)
240         (basename, ext) = os.path.splitext(os.path.basename(where))
241
242         if what != "all": title = what
243         else: title = basename
244         try:
245             graph = graphs[basename]
246         except KeyError:
247             graph = Graph(basename, title)
248             graphs[basename] = graph
249         continue
250
251     match = re_perf.match(line)
252     if match:
253         perfstr = match.group(3)
254         perf = perfstr.split()
255         col = perf[0]
256         try:
257             val = float(perf[1])
258         except ValueError:
259             val = None
260         try:
261             units = perf[2]
262             if '=' in units: units = None
263         except:
264             units = None
265         if match.group(4) != "ok":
266             val=None
267
268         graph.addValue(date, col, val, units)
269
270         match = re_perfaxis.search(perfstr)
271         if match:
272             graph.setAxis(col, match.group(1));
273
274 graphs = [g for g in list(graphs.values()) if len(g.columns)]
275 graphs = sorted(graphs, key=lambda g: g.title.lower())
276
277 for g in graphs:
278     g.findRanges()
279     g.fixupAxisNumbers()
280
281 pagetitle="Linux CAN subsystem performance plots"
282 print("""
283 <!DOCTYPE HTML>
284 <html>
285   <head>
286   <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
287   <title>%(pagetitle)s</title>
288
289   <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script>
290   <script type="text/javascript">
291     function tooltip_formatter() {
292       var s = '<b>'+ Highcharts.dateFormat('%%a, %%d %%b %%Y %%H:%%M:%%S', this.x) +'</b><br/>';
293       s += commit2msg[date2commit[this.x]];
294       $.each(this.points, function(i, point) {
295         s += '<br/><span style="color:'+ point.series.color+';">'+ point.series.name +'</span>: '+point.y;
296       });
297       return s;
298     }
299     function series_onclick(event) {
300       var prevpoint = null;
301       for (var i in this.data) {
302         if (event.point == this.data[i]) {
303           if (i > 0) prevpoint = this.data[i-1];
304           break;
305         }
306       }
307       if (prevpoint && date2commit[prevpoint.x] != date2commit[event.point.x])
308           window.location = "https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/log/?qt=range&q="+date2commit[prevpoint.x]+'..'+date2commit[event.point.x];
309       else
310           window.location = "https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/log/?id="+date2commit[event.point.x];
311     }""" % locals())
312
313 def make_int_keys(json):
314     r = re.compile('"(\d+)"(.*)')
315     s = ''
316     for match in r.finditer(json):
317         s += match.expand('\\1\\2')
318     return s
319
320 print("var date2commit = {%s};" % ",\n".join(["%d: '%s'" % (dateConv(k), date2commit[k]) for k in sorted(date2commit.keys())]))
321 print("var commit2msg = %s;" % json.dumps(commit2msg, indent=True))
322 # for d in sorted(date2commit.keys()):
323 #     v = commits[d];
324 #     print('\t%d: { msg: "%s", hash: "%s" },' % (1000*time.mktime(d), v[0].replace('"', '\\"'), str(v[1]).replace('"', '\\"')))
325 print("""
326         </script>
327         <script type="text/javascript" src="js/highstock.js"></script>
328     </head>
329
330     <body>
331         <h1>%(pagetitle)s</h1>
332     <p>The graphs below show performance numbers from various
333     CAN bus related benchmarks run on different Linux kernel versions.</p>
334     <p>Table of content:</p>
335         <ul>
336 """ % locals())
337 for graph in graphs:
338     print("     <li><a href='#%s'>%s</a></li>" % (graph.id, graph.title))
339 print("    </ul>")
340 for graph in graphs:
341     print("""
342     <h2><a name='%(id)s'>%(title)s</a></h2>
343         <div id="%(id)s" style="height: 400px"></div>
344     <script>
345 $.getJSON('%(dataname)s', function(data) {
346     var options = %(options)s
347     for (var i=0; i < data.length; i++)
348         options['series'][i]['data'] = data[i];
349     $("#%(id)s").highcharts("StockChart", options);
350 });
351 </script>
352 """ % {'id': graph.id,
353        'title': graph.title,
354        'dataname': graph.dataname,
355        'options': graph.options_json()})
356     print(graph.getData(), file=open(graph.dataname, 'w'))
357 print("Generated on "+str(datetime.now()))
358 print("""
359     </body>
360 </html>
361 """)
362
363 # Local Variables:
364 # compile-command: "make perf"
365 # End: