]> rtime.felk.cvut.cz Git - nul-nightly.git/blob - wvperf2html.py
Add log processing scripts here
[nul-nightly.git] / 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: (.*)')
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             low = None
125             high = None
126             all_in_range = True
127             for col in cols:
128                 values = np.array([row[col.name] for row in self.rows if row[col.name] != None], np.float64)
129                 if low == None and high == None:
130                     lastmonth = values[-30:]
131                     median = np.median(lastmonth)
132                     low  = median * 0.95
133                     high = median * 1.05
134
135                 if (values > high).any() or (values < low).any():
136                     all_in_range = False
137             if all_in_range:
138                 axis.yrange_max = high
139                 axis.yrange_min = low
140             else:
141                 axis.yrange_max = None
142                 axis.yrange_min = None
143
144     def fixupAxisNumbers(self):
145         # Sort axes according to the columns and number them
146         num = 0
147         for column in self.columns_ordered:
148             axis = column.axis
149             if axis not in self.axes_ordered:
150                 self.axes_ordered.insert(0, axis)
151                 axis.num = num
152                 num += 1
153         num = 0
154         for axis in self.axes_ordered:
155             axis.num = num
156             num += 1
157
158     def options_json(self):
159         options = {
160             'rangeSelector': {
161                 'selected': 2 # 6m
162             },
163             'title': {
164                 'text': self.title
165             },
166             'legend': {
167                 'enabled': True,
168                 'floating': False,
169                 'verticalAlign': "top",
170                 'x': 100,
171                 'y': 60,
172             },
173             'tooltip': {
174                 'formatter': "FUN(tooltip_formatter)END",
175                 'style': {
176                                     'whiteSpace': 'normal',
177                                     'width': '400px',
178                     },
179             },
180             'plotOptions': {
181                 'series': {
182                             'events': {
183                         'click': "FUN(series_onclick)END",
184                             }
185                 }
186             },
187             'yAxis': [{
188                     'lineWidth': 1,
189                     'labels': { 'align': 'right',
190                         'x': -3 },
191                     'title': { 'text': axis.getLabel() },
192                     'min': axis.yrange_min,
193                     'max': axis.yrange_max,
194                     } for axis in self.axes_ordered],
195             'series': [{ 'name': '%s [%s]' % (col.name, col.units),
196                  'yAxis': col.axis.num }
197                 for col in self.columns_ordered]
198             }
199         return json.dumps(options, indent=True).replace('"FUN(', '').replace(')END"', '')
200
201     def getData(self):
202         data = [[[row.getDate(), row[col.name]] for row in self.rows] for col in self.columns_ordered]
203         return json.dumps(data).replace('], [', '],\n[')
204
205
206 class Graphs(dict):
207     pass
208
209 graphs = Graphs()
210 date2commit = {}
211 commit2msg = {}
212
213 date = time.localtime(time.time())
214
215 for line in sys.stdin:
216     line = line.rstrip()
217
218     match = re_date.match(line)
219     if (match):
220         date = time.strptime(match.group(1), "%a, %d %b %Y %H:%M:%S +0200")
221         continue
222
223     match = re_testing.match(line)
224     if match:
225         what = match.group(2)
226         where = match.group(3)
227
228         match = re_commit.match(what)
229         if match:
230             date = time.strptime(match.group(1), "%Y-%m-%d %H:%M:%S")
231             commit = match.group(2)
232             match = re_commithash.search(commit);
233             if match:
234                 commithash = match.group(1)
235             else:
236                 commithash = None
237             date2commit[dateConv(date)] = commithash
238             commit2msg[commithash] = commit
239
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 print("""
282 <!DOCTYPE HTML>
283 <html>
284   <head>
285   <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
286   <title>NUL Performance Plots</title>
287
288   <script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1.6.1/jquery.min.js"></script>
289   <script type="text/javascript">
290     function tooltip_formatter() {
291       var s = '<b>'+ Highcharts.dateFormat('%a, %d %b %Y %H:%M:%S', this.x) +'</b><br/>';
292       s += commit2msg[date2commit[this.x]];
293       $.each(this.points, function(i, point) {
294         s += '<br/><span style="color:'+ point.series.color+';">'+ point.series.name +'</span>: '+point.y;
295       });
296       return s;
297     }
298     function series_onclick(event) {
299       var lastpoint = null;
300       for (var i in this.data) {
301         if (event.point == this.data[i]) {
302           if (i > 0) lastpoint = this.data[i-1];
303           break;
304         }
305       }
306       if (lastpoint)
307         window.location = "http://os.inf.tu-dresden.de/~jsteckli/cgi-bin/cgit.cgi/nul/log/?qt=range&q="+date2commit[lastpoint.x]+'..'+date2commit[event.point.x].hash;
308       else
309         window.location = "http://os.inf.tu-dresden.de/~jsteckli/cgi-bin/cgit.cgi/nul/log/?id="+date2commit[event.point.x];
310     }""")
311
312 def make_int_keys(json):
313     r = re.compile('"(\d+)"(.*)')
314     s = ''
315     for match in r.finditer(json):
316         s += match.expand('\\1\\2')
317     return s
318
319 print("var date2commit = {%s};" % ",\n".join(["%d: '%s'" % (k, date2commit[k]) for k in sorted(date2commit.keys())]))
320 print("var commit2msg = %s;" % json.dumps(commit2msg, indent=True))
321 # for d in sorted(date2commit.keys()):
322 #     v = commits[d];
323 #     print('\t%d: { msg: "%s", hash: "%s" },' % (1000*time.mktime(d), v[0].replace('"', '\\"'), str(v[1]).replace('"', '\\"')))
324 print("""
325         </script>
326         <script type="text/javascript" src="js/highstock.js"></script>
327     </head>
328
329     <body>
330         <h1>NUL Performance Plots</h1>
331     <p>The graphs below show performance numbers from various
332     benchmarks that run nightly on NUL repository.</p>
333     <p>Table of content:</p>
334         <ul>
335 """)
336 for graph in graphs:
337     print("     <li><a href='#%s'>%s</a></li>" % (graph.title, graph.title))
338 print("    </ul>")
339 for graph in graphs:
340     print("""
341     <h2><a name='%(title)s'>%(title)s</a></h2>
342         <div id="%(id)s" style="height: 400px"></div>
343     <script>
344 $.getJSON('%(dataname)s', function(data) {
345     var options = %(options)s
346     for (var i=0; i < data.length; i++)
347         options['series'][i]['data'] = data[i];
348     $("#%(id)s").highcharts("StockChart", options);
349 });
350 </script>
351 """ % {'id': graph.id,
352        'title': graph.title,
353        'dataname': graph.dataname,
354        'options': graph.options_json()})
355     print(graph.getData(), file=open(graph.dataname, 'w'))
356 print("""
357     </body>
358 </html>
359 """)
360
361 # Local Variables:
362 # compile-command: "cat nul-nightly/nul_*.log|./wvperfpreprocess.py|./wvperf2html.py > graphs.html"
363 # End: