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.
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.
13 from __future__ import print_function
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="([^"]+)"')
34 d = time.gmtime(time.mktime(date))
35 return int(time.mktime(d))*1000
39 def __init__(self, name=None, units=None):
44 if self.units and self.name:
45 return "%s [%s]" % (self.name, self.units)
50 def __repr__(self): return "Axis(name=%s units=%s, id=%s)" % (self.name, self.units, hex(id(self)))
53 def __init__(self, name, units, axis):
57 def __repr__(self): return "Column(name=%s units=%s axis=%s)" % (self.name, self.units, repr(self.axis))
60 def __init__(self, graph, date):
63 def __getitem__(self, column):
65 return dict.__getitem__(self, column)
69 return dateConv(self.date)
72 def __init__(self, id, title):
74 self.columns_ordered = []
80 self.axes_ordered = []
81 self.dataname = title.translate(string.maketrans(" /", "--"), "\"':,+()").lower()+".json"
83 def __getitem__(self, date):
85 rownum = self.date2row[date]
87 rownum = len(self.rows)
88 self.date2row[date] = rownum
90 return self.rows[rownum]
92 self.rows[rownum:rownum] = [Row(self, date)]
93 return self.rows[rownum]
95 def addValue(self, date, col, val, units):
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)
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
110 def addAxis(self, key, axis):
111 self.axes[key] = axis
114 def setAxis(self, col, key):
115 self.columns[col].axis = self.getAxis(key) or self.addAxis(key, Axis(name=key))
117 def getAxis(self, key):
118 if key not in self.axes: return None
119 return self.axes[key]
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]
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)
135 if (values > high).any() or (values < low).any():
138 axis.yrange_max = high
139 axis.yrange_min = low
141 axis.yrange_max = None
142 axis.yrange_min = None
144 def fixupAxisNumbers(self):
145 # Sort axes according to the columns and number them
147 for column in self.columns_ordered:
149 if axis not in self.axes_ordered:
150 self.axes_ordered.insert(0, axis)
154 for axis in self.axes_ordered:
158 def options_json(self):
169 'verticalAlign': "top",
174 'formatter': "FUN(tooltip_formatter)END",
176 'whiteSpace': 'normal',
183 'click': "FUN(series_onclick)END",
189 'labels': { 'align': 'right',
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]
199 return json.dumps(options, indent=True).replace('"FUN(', '').replace(')END"', '')
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[')
213 date = time.localtime(time.time())
215 for line in sys.stdin:
218 match = re_date.match(line)
220 date = time.strptime(match.group(1), "%a, %d %b %Y %H:%M:%S +0200")
223 match = re_testing.match(line)
225 what = match.group(2)
226 where = match.group(3)
228 match = re_commit.match(what)
230 date = time.strptime(match.group(1), "%Y-%m-%d %H:%M:%S")
231 commit = match.group(2)
232 match = re_commithash.search(commit);
234 commithash = match.group(1)
237 date2commit[dateConv(date)] = commithash
238 commit2msg[commithash] = commit
240 (basename, ext) = os.path.splitext(os.path.basename(where))
242 if what != "all": title = what
243 else: title = basename
245 graph = graphs[basename]
247 graph = Graph(basename, title)
248 graphs[basename] = graph
251 match = re_perf.match(line)
253 perfstr = match.group(3)
254 perf = perfstr.split()
262 if '=' in units: units = None
265 if match.group(4) != "ok":
268 graph.addValue(date, col, val, units)
270 match = re_perfaxis.search(perfstr)
272 graph.setAxis(col, match.group(1));
274 graphs = [g for g in list(graphs.values()) if len(g.columns)]
275 graphs = sorted(graphs, key=lambda g: g.title.lower())
285 <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
286 <title>NUL Performance Plots</title>
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;
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];
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;
309 window.location = "http://os.inf.tu-dresden.de/~jsteckli/cgi-bin/cgit.cgi/nul/log/?id="+date2commit[event.point.x];
312 def make_int_keys(json):
313 r = re.compile('"(\d+)"(.*)')
315 for match in r.finditer(json):
316 s += match.expand('\\1\\2')
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()):
323 # print('\t%d: { msg: "%s", hash: "%s" },' % (1000*time.mktime(d), v[0].replace('"', '\\"'), str(v[1]).replace('"', '\\"')))
326 <script type="text/javascript" src="js/highstock.js"></script>
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>
337 print(" <li><a href='#%s'>%s</a></li>" % (graph.title, graph.title))
341 <h2><a name='%(title)s'>%(title)s</a></h2>
342 <div id="%(id)s" style="height: 400px"></div>
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);
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'))
362 # compile-command: "cat nul-nightly/nul_*.log|./wvperfpreprocess.py|./wvperf2html.py > graphs.html"