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