Cleanup: Move js for treemap and aggregate_results into own js files.
[WebKit-https.git] / Tools / TestResultServer / static-dashboards / aggregate_results.js
1 // Copyright (C) 2013 Google Inc. All rights reserved.
2 //
3 // Redistribution and use in source and binary forms, with or without
4 // modification, are permitted provided that the following conditions are
5 // met:
6 //
7 //     * Redistributions of source code must retain the above copyright
8 // notice, this list of conditions and the following disclaimer.
9 //     * Redistributions in binary form must reproduce the above
10 // copyright notice, this list of conditions and the following disclaimer
11 // in the documentation and/or other materials provided with the
12 // distribution.
13 //     * Neither the name of Google Inc. nor the names of its
14 // contributors may be used to endorse or promote products derived from
15 // this software without specific prior written permission.
16 //
17 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20 // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21 // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22 // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23 // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27 // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 //
29 // @fileoverview Creates a dashboard for tracking number of passes/failures per run.
30 //
31 // Currently, only webkit tests are supported, but adding other test types
32 // should just require the following steps:
33 //     -generate results.json for these tests
34 //     -copy them to the appropriate location
35 //     -add the builder name to the list of builders in dashboard_base.js.
36
37 //////////////////////////////////////////////////////////////////////////////
38 // Methods and objects from dashboard_base.js to override.
39 //////////////////////////////////////////////////////////////////////////////
40 function generatePage()
41 {
42     var html = ui.html.testTypeSwitcher(true) + '<br>';
43     for (var builder in currentBuilders())
44         html += htmlForBuilder(builder);
45     document.body.innerHTML = html;
46 }
47
48 function handleValidHashParameter(key, value)
49 {
50     switch(key) {
51     case 'rawValues':
52         g_currentState[key] = value == 'true';
53         return true;
54
55     default:
56         return false;
57     }
58 }
59
60 g_defaultDashboardSpecificStateValues = {
61     rawValues: false
62 };
63
64 function htmlForBuilder(builder)
65 {
66     var results = g_resultsByBuilder[builder];
67     // Some keys were added later than others, so they don't have as many
68     // builds. Use the shortest.
69     // FIXME: Once 500 runs have finished, we can get rid of passing this
70     // around and just assume all keys have the same number of builders for a
71     // given builder.
72     var numColumns = results[ALL_FIXABLE_COUNT_KEY].length;
73     var html = '<div class=container><h2>' + builder + '</h2>';
74
75     if (g_currentState.rawValues)
76         html += rawValuesHTML(results, numColumns);
77     else {
78         html += '<a href="timeline_explorer.html' + (location.hash ? location.hash + '&' : '#') + 'builder=' + builder + '">' +
79             chartHTML(results, numColumns) + '</a>';
80     }
81
82     html += '</div>';
83     return html;
84 }
85
86 function rawValuesHTML(results, numColumns)
87 {
88     var html = htmlForSummaryTable(results, numColumns) +
89         htmlForTestType(results, FIXABLE_COUNTS_KEY, FIXABLE_DESCRIPTION, numColumns);
90     if (isLayoutTestResults()) {
91         html += htmlForTestType(results, DEFERRED_COUNTS_KEY, DEFERRED_DESCRIPTION, numColumns) +
92             htmlForTestType(results, WONTFIX_COUNTS_KEY, WONTFIX_DESCRIPTION, numColumns);
93     }
94     return html;
95 }
96
97 function chartHTML(results, numColumns)
98 {
99     var shouldShowWebKitRevisions = isTipOfTreeWebKitBuilder();
100     var revisionKey = shouldShowWebKitRevisions ? WEBKIT_REVISIONS_KEY : CHROME_REVISIONS_KEY;
101     var startRevision = results[revisionKey][numColumns - 1];
102     var endRevision = results[revisionKey][0];
103     var revisionLabel = shouldShowWebKitRevisions ? "WebKit Revision" : "Chromium Revision";
104
105     var fixable = results[FIXABLE_COUNT_KEY].slice(0, numColumns);
106     var html = chart("Total failing", {"": fixable}, revisionLabel, startRevision, endRevision);
107
108     var values = valuesPerExpectation(results[FIXABLE_COUNTS_KEY], numColumns);
109     // Don't care about number of passes for the charts.
110     delete(values['P']);
111
112     return html + chart("Detailed breakdown", values, revisionLabel, startRevision, endRevision);
113 }
114
115 var LABEL_COLORS = ['FF0000', '00FF00', '0000FF', '000000', 'FF6EB4', 'FFA812', '9B30FF', '00FFCC'];
116
117 // FIXME: Find a better way to exclude outliers. This is just so we exclude
118 // runs where every test failed.
119 var MAX_VALUE = 10000;
120
121 function filteredValues(values, desiredNumberOfPoints)
122 {
123     // Filter out values to make the graph a bit more readable and to keep URLs
124     // from exceeding the browsers max length restriction.
125     var filterAmount = Math.floor(values.length / desiredNumberOfPoints);
126     if (filterAmount < 1)
127         return values;
128
129     return values.filter(function(element, index, array) {
130         // Include the most recent and oldest values and exclude outliers.
131         return (index % filterAmount == 0 || index == array.length - 1) && (array[index] < MAX_VALUE && array[index] != 0);
132     });
133 }
134
135 function chartUrl(title, values, revisionLabel, startRevision, endRevision, desiredNumberOfPoints) {
136     var maxValue = 0;
137     for (var expectation in values)
138         maxValue = Math.max(maxValue, Math.max.apply(null, filteredValues(values[expectation], desiredNumberOfPoints)));
139
140     var chartData = '';
141     var labels = '';
142     var numLabels = 0;
143
144     var first = true;
145     for (var expectation in values) {
146         chartData += (first ? 'e:' : ',') + extendedEncode(filteredValues(values[expectation], desiredNumberOfPoints).reverse(), maxValue);
147
148         if (expectation) {
149             numLabels++;
150             labels += (first ? '' : '|') + expectationsMap()[expectation];
151         }
152         first = false;
153     }
154
155     var url = "http://chart.apis.google.com/chart?cht=lc&chs=600x400&chd=" +
156             chartData + "&chg=15,15,1,3&chxt=x,x,y&chxl=1:||" + revisionLabel +
157             "|&chxr=0," + startRevision + "," + endRevision + "|2,0," + maxValue + "&chtt=" + title;
158
159
160     if (labels)
161         url += "&chdl=" + labels + "&chco=" + LABEL_COLORS.slice(0, numLabels).join(',');
162     return url;
163 }
164
165 function chart(title, values, revisionLabel, startRevision, endRevision)
166 {
167     var desiredNumberOfPoints = 400;
168     var url = chartUrl(title, values, revisionLabel, startRevision, endRevision, desiredNumberOfPoints);
169
170     while (url.length >= 2048) {
171         // Decrease the desired number of points gradually until we get a URL that
172         // doesn't exceed chartserver's max URL length.
173         desiredNumberOfPoints = 3 / 4 * desiredNumberOfPoints;
174         url = chartUrl(title, values, revisionLabel, startRevision, endRevision, desiredNumberOfPoints);
175     }
176
177     return '<img src="' + url + '">';
178 }
179
180 function htmlForRevisionRows(results, numColumns)
181 {
182     return htmlForTableRow('WebKit Revision', results[WEBKIT_REVISIONS_KEY].slice(0, numColumns)) +
183         htmlForTableRow('Chrome Revision', results[CHROME_REVISIONS_KEY].slice(0, numColumns));
184 }
185
186 function wrapHTMLInTable(description, html)
187 {
188     return '<h3>' + description + '</h3><table><tbody>' + html + '</tbody></table>';
189 }
190
191 function htmlForSummaryTable(results, numColumns)
192 {
193     var percent = [];
194     var fixable = results[FIXABLE_COUNT_KEY].slice(0, numColumns);
195     var allFixable = results[ALL_FIXABLE_COUNT_KEY].slice(0, numColumns);
196     for (var i = 0; i < numColumns; i++) {
197         var percentage = 100 * (allFixable[i] - fixable[i]) / allFixable[i];
198         // Round to the nearest tenth of a percent.
199         percent.push(Math.round(percentage * 10) / 10 + '%');
200     }
201     var html = htmlForRevisionRows(results, numColumns) +
202         htmlForTableRow('Percent passed', percent) +
203         htmlForTableRow('Failures (deduped)', fixable) +
204         htmlForTableRow('Fixable Tests', allFixable);
205     return wrapHTMLInTable('Summary', html);
206 }
207
208 function valuesPerExpectation(counts, numColumns)
209 {
210     var values = {};
211     for (var i = 0; i < numColumns; i++) {
212         for (var expectation in expectationsMap()) {
213             if (expectation in counts[i]) {
214                 var count = counts[i][expectation];
215                 if (!values[expectation])
216                     values[expectation] = [];
217                 values[expectation].push(count);
218             }
219         }
220     }
221     return values;
222 }
223
224 function htmlForTestType(results, key, description, numColumns)
225 {
226     var counts = results[key];
227     var html = htmlForRevisionRows(results, numColumns);
228     var values = valuesPerExpectation(counts, numColumns);
229     for (var expectation in values)
230         html += htmlForTableRow(expectationsMap()[expectation], values[expectation]);
231     return wrapHTMLInTable(description, html);
232 }
233
234 function htmlForTableRow(columnName, values)
235 {
236     return '<tr><td>' + columnName + '</td><td>' + values.join('</td><td>') + '</td></tr>';
237 }
238
239 // Taken from http://code.google.com/apis/chart/docs/data_formats.html.
240 function extendedEncode(arrVals, maxVal)
241 {
242     var map = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-.';
243     var mapLength = map.length;
244     var mapLengthSquared = mapLength * mapLength;
245
246     var chartData = '';
247
248     for (var i = 0, len = arrVals.length; i < len; i++) {
249         // In case the array vals were translated to strings.
250         var numericVal = new Number(arrVals[i]);
251         // Scale the value to maxVal.
252         var scaledVal = Math.floor(mapLengthSquared * numericVal / maxVal);
253
254         if(scaledVal > mapLengthSquared - 1)
255             chartData += "..";
256         else if (scaledVal < 0)
257             chartData += '__';
258         else {
259             // Calculate first and second digits and add them to the output.
260             var quotient = Math.floor(scaledVal / mapLength);
261             var remainder = scaledVal - mapLength * quotient;
262             chartData += map.charAt(quotient) + map.charAt(remainder);
263         }
264     }
265
266     return chartData;
267 }
268
269 window.addEventListener('load', function() {
270     var resourceLoader = new loader.Loader(intializeHistory);
271     resourceLoader.load();
272 }, false);