results-template.html 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488
  1. <!DOCTYPE html>
  2. <html>
  3. <head>
  4. <title>Telemetry Performance Test Results</title>
  5. <style type="text/css">
  6. section {
  7. background: white;
  8. padding: 10px;
  9. position: relative;
  10. }
  11. .collapsed:before {
  12. color: #ccc;
  13. content: '\25B8\00A0';
  14. }
  15. .expanded:before {
  16. color: #eee;
  17. content: '\25BE\00A0';
  18. }
  19. .line-plots {
  20. padding-left: 25px;
  21. }
  22. .line-plots > div {
  23. display: inline-block;
  24. width: 90px;
  25. height: 40px;
  26. margin-right: 10px;
  27. }
  28. .lage-line-plots {
  29. padding-left: 25px;
  30. }
  31. .large-line-plots > div, .histogram-plots > div {
  32. display: inline-block;
  33. width: 400px;
  34. height: 200px;
  35. margin-right: 10px;
  36. }
  37. .large-line-plot-labels > div, .histogram-plot-labels > div {
  38. display: inline-block;
  39. width: 400px;
  40. height: 11px;
  41. margin-right: 10px;
  42. color: #545454;
  43. text-align: center;
  44. font-size: 11px;
  45. }
  46. .closeButton {
  47. display: inline-block;
  48. background: #eee;
  49. background: linear-gradient(rgb(220, 220, 220), rgb(255, 255, 255));
  50. border: inset 1px #ddd;
  51. border-radius: 4px;
  52. float: right;
  53. font-size: small;
  54. -webkit-user-select: none;
  55. font-weight: bold;
  56. padding: 1px 4px;
  57. }
  58. .closeButton:hover {
  59. background: #F09C9C;
  60. }
  61. .label {
  62. cursor: text;
  63. }
  64. .label:hover {
  65. background: #ffcc66;
  66. }
  67. section h1 {
  68. text-align: center;
  69. font-size: 1em;
  70. }
  71. section .tooltip {
  72. position: absolute;
  73. text-align: center;
  74. background: #ffcc66;
  75. border-radius: 5px;
  76. padding: 0px 5px;
  77. }
  78. body {
  79. padding: 0px;
  80. margin: 0px;
  81. font-family: sans-serif;
  82. }
  83. table {
  84. background: white;
  85. width: 100%;
  86. }
  87. table, td, th {
  88. border-collapse: collapse;
  89. padding: 5px;
  90. white-space: nowrap;
  91. }
  92. .highlight:hover {
  93. color: #202020;
  94. background: #e0e0e0;
  95. }
  96. .nestedRow {
  97. background: #f8f8f8;
  98. }
  99. .importantNestedRow {
  100. background: #e0e0e0;
  101. font-weight: bold;
  102. }
  103. table td {
  104. position: relative;
  105. }
  106. th, td {
  107. cursor: pointer;
  108. cursor: hand;
  109. }
  110. th {
  111. background: #e6eeee;
  112. background: linear-gradient(rgb(244, 244, 244), rgb(217, 217, 217));
  113. border: 1px solid #ccc;
  114. }
  115. th.sortUp:after {
  116. content: ' \25BE';
  117. }
  118. th.sortDown:after {
  119. content: ' \25B4';
  120. }
  121. td.comparison, td.result {
  122. text-align: right;
  123. }
  124. td.better {
  125. color: #6c6;
  126. }
  127. td.fadeOut {
  128. opacity: 0.5;
  129. }
  130. td.unknown {
  131. color: #ccc;
  132. }
  133. td.worse {
  134. color: #c66;
  135. }
  136. td.reference {
  137. font-style: italic;
  138. font-weight: bold;
  139. color: #444;
  140. }
  141. td.missing {
  142. color: #ccc;
  143. text-align: center;
  144. }
  145. td.missingReference {
  146. color: #ccc;
  147. text-align: center;
  148. font-style: italic;
  149. }
  150. .checkbox {
  151. display: inline-block;
  152. background: #eee;
  153. background: linear-gradient(rgb(220, 220, 220), rgb(200, 200, 200));
  154. border: inset 1px #ddd;
  155. border-radius: 5px;
  156. margin: 10px;
  157. font-size: small;
  158. cursor: pointer;
  159. cursor: hand;
  160. -webkit-user-select: none;
  161. font-weight: bold;
  162. }
  163. .checkbox span {
  164. display: inline-block;
  165. line-height: 100%;
  166. padding: 5px 8px;
  167. border: outset 1px transparent;
  168. }
  169. .checkbox .checked {
  170. background: #e6eeee;
  171. background: linear-gradient(rgb(255, 255, 255), rgb(235, 235, 235));
  172. border: outset 1px #eee;
  173. border-radius: 5px;
  174. }
  175. .openAllButton {
  176. display: inline-block;
  177. colour: #6c6
  178. background: #eee;
  179. background: linear-gradient(rgb(220, 220, 220), rgb(255, 255, 255));
  180. border: inset 1px #ddd;
  181. border-radius: 5px;
  182. float: left;
  183. font-size: small;
  184. -webkit-user-select: none;
  185. font-weight: bold;
  186. padding: 1px 4px;
  187. }
  188. .openAllButton:hover {
  189. background: #60f060;
  190. }
  191. .closeAllButton {
  192. display: inline-block;
  193. colour: #c66
  194. background: #eee;
  195. background: linear-gradient(rgb(220, 220, 220),rgb(255, 255, 255));
  196. border: inset 1px #ddd;
  197. border-radius: 5px;
  198. float: left;
  199. font-size: small;
  200. -webkit-user-select: none;
  201. font-weight: bold;
  202. padding: 1px 4px;
  203. }
  204. .closeAllButton:hover {
  205. background: #f04040;
  206. }
  207. </style>
  208. </head>
  209. <body onload="init()">
  210. <div style="padding: 0 10px; white-space: nowrap;">
  211. Result <span id="time-memory" class="checkbox"></span>
  212. Reference <span id="reference" class="checkbox"></span>
  213. Style <span id="scatter-line" class="checkbox"><span class="checked">Scatter</span><span>Line</span></span>
  214. <span class="checkbox"><span class="checked" id="undelete">Undelete</span></span><br>
  215. Run your test with --reset-results to clear all runs
  216. </div>
  217. <table id="container"></table>
  218. <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script>
  219. <script>
  220. %plugins%
  221. </script>
  222. <script>
  223. var EXPANDED = true;
  224. var COLLAPSED = false;
  225. var SMALLEST_PERCENT_DISPLAYED = 0.01;
  226. var INVISIBLE = false;
  227. var VISIBLE = true;
  228. var COMPARISON_SUFFIX = '_compare';
  229. var SORT_DOWN_CLASS = 'sortDown';
  230. var SORT_UP_CLASS = 'sortUp';
  231. var BETTER_CLASS = 'better';
  232. var WORSE_CLASS = 'worse';
  233. var UNKNOWN_CLASS = 'unknown'
  234. // px Indentation for graphs
  235. var GRAPH_INDENT = 64;
  236. var PADDING_UNDER_GRAPH = 5;
  237. // px Indentation for nested children left-margins
  238. var INDENTATION = 40;
  239. function TestResult(metric, values, associatedRun, std, degreesOfFreedom) {
  240. if (values) {
  241. if (values[0] instanceof Array) {
  242. var flattenedValues = [];
  243. for (var i = 0; i < values.length; i++)
  244. flattenedValues = flattenedValues.concat(values[i]);
  245. values = flattenedValues;
  246. }
  247. if (jQuery.type(values[0]) === 'string') {
  248. try {
  249. var current = JSON.parse(values[0]);
  250. if (current.params.type === 'HISTOGRAM') {
  251. this.histogramValues = current;
  252. // Histogram results have no values (per se). Instead we calculate
  253. // the values from the histogram bins.
  254. var values = [];
  255. var buckets = current.buckets
  256. for (var i = 0; i < buckets.length; i++) {
  257. var bucket = buckets[i];
  258. var bucket_mean = (bucket.high + bucket.low) / 2;
  259. for (var b = 0; b < bucket.count; b++) {
  260. values.push(bucket_mean);
  261. }
  262. }
  263. }
  264. }
  265. catch (e) {
  266. console.error(e, e.stack);
  267. }
  268. }
  269. } else {
  270. values = [];
  271. }
  272. this.test = function() { return metric; }
  273. this.values = function() { return values.map(function(value) { return metric.scalingFactor() * value; }); }
  274. this.unscaledMean = function() { return Statistics.sum(values) / values.length; }
  275. this.mean = function() { return metric.scalingFactor() * this.unscaledMean(); }
  276. this.min = function() { return metric.scalingFactor() * Statistics.min(values); }
  277. this.max = function() { return metric.scalingFactor() * Statistics.max(values); }
  278. this.confidenceIntervalDelta = function() {
  279. if (std !== undefined) {
  280. return metric.scalingFactor() * Statistics.confidenceIntervalDeltaFromStd(0.95, values.length,
  281. std, degreesOfFreedom);
  282. }
  283. return metric.scalingFactor() * Statistics.confidenceIntervalDelta(0.95, values.length,
  284. Statistics.sum(values), Statistics.squareSum(values));
  285. }
  286. this.confidenceIntervalDeltaRatio = function() { return this.confidenceIntervalDelta() / this.mean(); }
  287. this.percentDifference = function(other) {
  288. if (other === undefined) {
  289. return undefined;
  290. }
  291. return (other.unscaledMean() - this.unscaledMean()) / this.unscaledMean();
  292. }
  293. this.isStatisticallySignificant = function(other) {
  294. if (other === undefined) {
  295. return false;
  296. }
  297. var diff = Math.abs(other.mean() - this.mean());
  298. return diff > this.confidenceIntervalDelta() && diff > other.confidenceIntervalDelta();
  299. }
  300. this.run = function() { return associatedRun; }
  301. }
  302. function TestRun(entry) {
  303. this.id = function() { return entry['buildTime'].replace(/[:.-]/g,''); }
  304. this.label = function() {
  305. if (labelKey in localStorage)
  306. return localStorage[labelKey];
  307. return entry['label'];
  308. }
  309. this.setLabel = function(label) { localStorage[labelKey] = label; }
  310. this.isHidden = function() { return localStorage[hiddenKey]; }
  311. this.hide = function() { localStorage[hiddenKey] = true; }
  312. this.show = function() { localStorage.removeItem(hiddenKey); }
  313. this.description = function() {
  314. return new Date(entry['buildTime']).toLocaleString() + '\n' + entry['platform'] + ' ' + this.label();
  315. }
  316. var labelKey = 'telemetry_label_' + this.id();
  317. var hiddenKey = 'telemetry_hide_' + this.id();
  318. }
  319. function PerfTestMetric(name, metric, unit, isImportant) {
  320. var testResults = [];
  321. var cachedUnit = null;
  322. var cachedScalingFactor = null;
  323. // We can't do this in TestResult because all results for each test need to share the same unit and the same scaling factor.
  324. function computeScalingFactorIfNeeded() {
  325. // FIXME: We shouldn't be adjusting units on every test result.
  326. // We can only do this on the first test.
  327. if (!testResults.length || cachedUnit)
  328. return;
  329. var mean = testResults[0].unscaledMean(); // FIXME: We should look at all values.
  330. var kilo = unit == 'bytes' ? 1024 : 1000;
  331. if (mean > 10 * kilo * kilo && unit != 'ms') {
  332. cachedScalingFactor = 1 / kilo / kilo;
  333. cachedUnit = 'M ' + unit;
  334. } else if (mean > 10 * kilo) {
  335. cachedScalingFactor = 1 / kilo;
  336. cachedUnit = unit == 'ms' ? 's' : ('K ' + unit);
  337. } else {
  338. cachedScalingFactor = 1;
  339. cachedUnit = unit;
  340. }
  341. }
  342. this.name = function() { return name + ':' + metric; }
  343. this.isImportant = isImportant;
  344. this.isMemoryTest = function() {
  345. return (unit == 'kb' ||
  346. unit == 'KB' ||
  347. unit == 'MB' ||
  348. unit == 'bytes' ||
  349. unit == 'count' ||
  350. !metric.indexOf('V8.'));
  351. }
  352. this.addResult = function(newResult) {
  353. testResults.push(newResult);
  354. cachedUnit = null;
  355. cachedScalingFactor = null;
  356. }
  357. this.results = function() { return testResults; }
  358. this.scalingFactor = function() {
  359. computeScalingFactorIfNeeded();
  360. return cachedScalingFactor;
  361. }
  362. this.unit = function() {
  363. computeScalingFactorIfNeeded();
  364. return cachedUnit;
  365. }
  366. this.biggerIsBetter = function() {
  367. if (window.unitToBiggerIsBetter == undefined) {
  368. window.unitToBiggerIsBetter = {};
  369. var units = JSON.parse(document.getElementById('units-json').textContent);
  370. for (var u in units) {
  371. if (units[u].improvement_direction == 'up') {
  372. window.unitToBiggerIsBetter[u] = true;
  373. }
  374. }
  375. }
  376. return window.unitToBiggerIsBetter[unit];
  377. }
  378. }
  379. function UndeleteManager() {
  380. var key = 'telemetry_undeleteIds'
  381. var undeleteIds = localStorage[key];
  382. if (undeleteIds) {
  383. undeleteIds = JSON.parse(undeleteIds);
  384. } else {
  385. undeleteIds = [];
  386. }
  387. this.ondelete = function(id) {
  388. undeleteIds.push(id);
  389. localStorage[key] = JSON.stringify(undeleteIds);
  390. }
  391. this.undeleteMostRecent = function() {
  392. if (!this.mostRecentlyDeletedId())
  393. return;
  394. undeleteIds.pop();
  395. localStorage[key] = JSON.stringify(undeleteIds);
  396. }
  397. this.mostRecentlyDeletedId = function() {
  398. if (!undeleteIds.length)
  399. return undefined;
  400. return undeleteIds[undeleteIds.length-1];
  401. }
  402. }
  403. var undeleteManager = new UndeleteManager();
  404. var plotColor = 'rgb(230,50,50)';
  405. var subpointsPlotOptions = {
  406. lines: {show:true, lineWidth: 0},
  407. color: plotColor,
  408. points: {show: true, radius: 1},
  409. bars: {show: false}};
  410. var mainPlotOptions = {
  411. xaxis: {
  412. min: -0.5,
  413. tickSize: 1,
  414. },
  415. crosshair: { mode: 'y' },
  416. series: { shadowSize: 0 },
  417. bars: {show: true, align: 'center', barWidth: 0.5},
  418. lines: { show: false },
  419. points: { show: true },
  420. grid: {
  421. borderWidth: 1,
  422. borderColor: '#ccc',
  423. backgroundColor: '#fff',
  424. hoverable: true,
  425. autoHighlight: false,
  426. }
  427. };
  428. var linePlotOptions = {
  429. yaxis: { show: false },
  430. xaxis: { show: false },
  431. lines: { show: true },
  432. grid: { borderWidth: 1, borderColor: '#ccc' },
  433. colors: [ plotColor ]
  434. };
  435. var largeLinePlotOptions = {
  436. xaxis: {
  437. show: true,
  438. tickDecimals: 0,
  439. },
  440. lines: { show: true },
  441. grid: { borderWidth: 1, borderColor: '#ccc' },
  442. colors: [ plotColor ]
  443. };
  444. var histogramPlotOptions = {
  445. bars: {show: true, fill: 1}
  446. };
  447. function createPlot(container, test, useLargeLinePlots) {
  448. if (test.results()[0].histogramValues) {
  449. var section = $('<section><div class="histogram-plots"></div>'
  450. + '<div class="histogram-plot-labels"></div>'
  451. + '<span class="tooltip"></span></section>');
  452. $(container).append(section);
  453. attachHistogramPlots(test, section.children('.histogram-plots'));
  454. }
  455. else if (useLargeLinePlots) {
  456. var section = $('<section><div class="large-line-plots"></div>'
  457. + '<div class="large-line-plot-labels"></div>'
  458. + '<span class="tooltip"></span></section>');
  459. $(container).append(section);
  460. attachLinePlots(test, section.children('.large-line-plots'), useLargeLinePlots);
  461. attachLinePlotLabels(test, section.children('.large-line-plot-labels'));
  462. } else {
  463. var section = $('<section><div class="plot"></div><div class="line-plots"></div>'
  464. + '<span class="tooltip"></span></section>');
  465. section.children('.plot').css({'width': (100 * test.results().length + 25) + 'px', 'height': '300px'});
  466. $(container).append(section);
  467. var plotContainer = section.children('.plot');
  468. var minIsZero = true;
  469. attachPlot(test, plotContainer, minIsZero);
  470. attachLinePlots(test, section.children('.line-plots'), useLargeLinePlots);
  471. var tooltip = section.children('.tooltip');
  472. plotContainer.bind('plothover', function(event, position, item) {
  473. if (item) {
  474. var postfix = item.series.id ? ' (' + item.series.id + ')' : '';
  475. tooltip.html(item.datapoint[1].toPrecision(4) + postfix);
  476. var sectionOffset = $(section).offset();
  477. tooltip.css({left: item.pageX - sectionOffset.left - tooltip.outerWidth() / 2, top: item.pageY - sectionOffset.top + 10});
  478. tooltip.fadeIn(200);
  479. } else
  480. tooltip.hide();
  481. });
  482. plotContainer.mouseout(function() {
  483. tooltip.hide();
  484. });
  485. plotContainer.click(function(event) {
  486. event.preventDefault();
  487. minIsZero = !minIsZero;
  488. attachPlot(test, plotContainer, minIsZero);
  489. });
  490. }
  491. return section;
  492. }
  493. function attachLinePlots(test, container, useLargeLinePlots) {
  494. var results = test.results();
  495. var attachedPlot = false;
  496. if (useLargeLinePlots) {
  497. var maximum = 0;
  498. for (var i = 0; i < results.length; i++) {
  499. var values = results[i].values();
  500. if (!values)
  501. continue;
  502. var local_max = Math.max.apply(Math, values);
  503. if (local_max > maximum)
  504. maximum = local_max;
  505. }
  506. }
  507. for (var i = 0; i < results.length; i++) {
  508. container.append('<div></div>');
  509. var values = results[i].values();
  510. if (!values)
  511. continue;
  512. attachedPlot = true;
  513. if (useLargeLinePlots) {
  514. var options = $.extend(true, {}, largeLinePlotOptions,
  515. {yaxis: {min: 0.0, max: maximum},
  516. xaxis: {min: 0.0, max: values.length - 1},
  517. points: {show: (values.length < 2) ? true : false}});
  518. } else {
  519. var options = $.extend(true, {}, linePlotOptions,
  520. {yaxis: {min: Math.min.apply(Math, values) * 0.9, max: Math.max.apply(Math, values) * 1.1},
  521. xaxis: {min: -0.5, max: values.length - 0.5},
  522. points: {show: (values.length < 2) ? true : false}});
  523. }
  524. $.plot(container.children().last(), [values.map(function(value, index) { return [index, value]; })], options);
  525. }
  526. if (!attachedPlot)
  527. container.children().remove();
  528. }
  529. function attachHistogramPlots(test, container) {
  530. var results = test.results();
  531. var attachedPlot = false;
  532. for (var i = 0; i < results.length; i++) {
  533. container.append('<div></div>');
  534. var histogram = results[i].histogramValues
  535. if (!histogram)
  536. continue;
  537. attachedPlot = true;
  538. var buckets = histogram.buckets
  539. var bucket;
  540. var max_count = 0;
  541. for (var j = 0; j < buckets.length; j++) {
  542. bucket = buckets[j];
  543. max_count = Math.max(max_count, bucket.count);
  544. }
  545. var xmax = bucket.high * 1.1;
  546. var ymax = max_count * 1.1;
  547. var options = $.extend(true, {}, histogramPlotOptions,
  548. {yaxis: {min: 0.0, max: ymax},
  549. xaxis: {min: histogram.params.min, max: xmax}});
  550. var plot = $.plot(container.children().last(), [[]], options);
  551. // Flot only supports fixed with bars and our histogram's buckets are
  552. // variable width, so we need to do our own bar drawing.
  553. var ctx = plot.getCanvas().getContext("2d");
  554. ctx.lineWidth="1";
  555. ctx.fillStyle = "rgba(255, 0, 0, 0.2)";
  556. ctx.strokeStyle="red";
  557. for (var j = 0; j < buckets.length; j++) {
  558. bucket = buckets[j];
  559. var bl = plot.pointOffset({ x: bucket.low, y: 0});
  560. var tr = plot.pointOffset({ x: bucket.high, y: bucket.count});
  561. ctx.fillRect(bl.left, bl.top, tr.left - bl.left, tr.top - bl.top);
  562. ctx.strokeRect(bl.left, bl.top, tr.left - bl.left, tr.top - bl.top);
  563. }
  564. }
  565. if (!attachedPlot)
  566. container.children().remove();
  567. }
  568. function attachLinePlotLabels(test, container) {
  569. var results = test.results();
  570. var attachedPlot = false;
  571. for (var i = 0; i < results.length; i++) {
  572. container.append('<div>' + results[i].run().label() + '</div>');
  573. }
  574. }
  575. function attachPlot(test, plotContainer, minIsZero) {
  576. var results = test.results();
  577. var values = results.reduce(function(values, result, index) {
  578. var newValues = result.values();
  579. return newValues ? values.concat(newValues.map(function(value) { return [index, value]; })) : values;
  580. }, []);
  581. var plotData = [$.extend(true, {}, subpointsPlotOptions, {data: values})];
  582. plotData.push({id: '&mu;', data: results.map(function(result, index) { return [index, result.mean()]; }), color: plotColor});
  583. var overallMax = Statistics.max(results.map(function(result, index) { return result.max(); }));
  584. var overallMin = Statistics.min(results.map(function(result, index) { return result.min(); }));
  585. var margin = (overallMax - overallMin) * 0.1;
  586. var currentPlotOptions = $.extend(true, {}, mainPlotOptions, {yaxis: {
  587. min: minIsZero ? 0 : overallMin - margin,
  588. max: minIsZero ? overallMax * 1.1 : overallMax + margin}});
  589. currentPlotOptions.xaxis.max = results.length - 0.5;
  590. currentPlotOptions.xaxis.ticks = results.map(function(result, index) { return [index, result.run().label()]; });
  591. $.plot(plotContainer, plotData, currentPlotOptions);
  592. }
  593. function toFixedWidthPrecision(value) {
  594. var decimal = value.toFixed(2);
  595. return decimal;
  596. }
  597. function formatPercentage(fraction) {
  598. var percentage = fraction * 100;
  599. return (fraction * 100).toFixed(2) + '%';
  600. }
  601. function setUpSortClicks(runs)
  602. {
  603. $('#nameColumn').click(sortByName);
  604. $('#unitColumn').click(sortByUnit);
  605. runs.forEach(function(run) {
  606. $('#' + run.id()).click(sortByResult);
  607. $('#' + run.id() + COMPARISON_SUFFIX).click(sortByReference);
  608. });
  609. }
  610. function TestTypeSelector(tests) {
  611. this.recognizers = {
  612. 'Time': function(test) { return !test.isMemoryTest(); },
  613. 'Memory': function(test) { return test.isMemoryTest(); }
  614. };
  615. this.testTypeNames = this.generateUsedTestTypeNames(tests);
  616. // Default to selecting the first test-type name in the list.
  617. this.testTypeName = this.testTypeNames[0];
  618. }
  619. TestTypeSelector.prototype = {
  620. set testTypeName(testTypeName) {
  621. this._testTypeName = testTypeName;
  622. this.shouldShowTest = this.recognizers[testTypeName];
  623. },
  624. generateUsedTestTypeNames: function(allTests) {
  625. var testTypeNames = [];
  626. for (var recognizedTestName in this.recognizers) {
  627. var recognizes = this.recognizers[recognizedTestName];
  628. for (var testName in allTests) {
  629. var test = allTests[testName];
  630. if (recognizes(test)) {
  631. testTypeNames.push(recognizedTestName);
  632. break;
  633. }
  634. }
  635. }
  636. if (testTypeNames.length === 0) {
  637. // No test types we recognize, add 'No Results' with a dummy recognizer.
  638. var noResults = 'No Results';
  639. this.recognizers[noResults] = function() { return false; };
  640. testTypeNames.push(noResults);
  641. } else if (testTypeNames.length > 1) {
  642. // We have more than one test type, so add 'All' with a recognizer that always succeeds.
  643. var allResults = 'All';
  644. this.recognizers[allResults] = function() { return true; };
  645. testTypeNames.push(allResults);
  646. }
  647. return testTypeNames;
  648. },
  649. buildButtonHTMLForUsedTestTypes: function() {
  650. var selectedTestTypeName = this._testTypeName;
  651. // Build spans for all recognised test names with the selected test highlighted.
  652. return this.testTypeNames.map(function(testTypeName) {
  653. var classAttribute = testTypeName === selectedTestTypeName ? ' class=checked' : '';
  654. return '<span' + classAttribute + '>' + testTypeName + '</span>';
  655. }).join('');
  656. }
  657. };
  658. var topLevelRows;
  659. var allTableRows;
  660. function displayTable(tests, runs, testTypeSelector, referenceIndex, useLargeLinePlots) {
  661. var resultHeaders = runs.map(function(run, index) {
  662. var header = '<th id="' + run.id() + '" ' +
  663. 'colspan=2 ' +
  664. 'title="' + run.description() + '">' +
  665. '<span class="label" ' +
  666. 'title="Edit run label">' +
  667. run.label() +
  668. '</span>' +
  669. '<div class="closeButton" ' +
  670. 'title="Delete run">' +
  671. '&times;' +
  672. '</div>' +
  673. '</th>';
  674. if (index !== referenceIndex) {
  675. header += '<th id="' + run.id() + COMPARISON_SUFFIX + '" ' +
  676. 'title="Sort by better/worse">' +
  677. '&Delta;' +
  678. '</th>';
  679. }
  680. return header;
  681. });
  682. resultHeaders = resultHeaders.join('');
  683. htmlString = '<thead>' +
  684. '<tr>' +
  685. '<th id="nameColumn">' +
  686. '<div class="openAllButton" ' +
  687. 'title="Open all rows or graphs">' +
  688. 'Open All' +
  689. '</div>' +
  690. '<div class="closeAllButton" ' +
  691. 'title="Close all rows">' +
  692. 'Close All' +
  693. '</div>' +
  694. 'Test' +
  695. '</th>' +
  696. '<th id="unitColumn">' +
  697. 'Unit' +
  698. '</th>' +
  699. resultHeaders +
  700. '</tr>' +
  701. '</head>' +
  702. '<tbody>' +
  703. '</tbody>';
  704. $('#container').html(htmlString);
  705. var testNames = [];
  706. for (testName in tests)
  707. testNames.push(testName);
  708. allTableRows = [];
  709. testNames.forEach(function(testName) {
  710. var test = tests[testName];
  711. if (testTypeSelector.shouldShowTest(test)) {
  712. allTableRows.push(new TableRow(runs, test, referenceIndex, useLargeLinePlots));
  713. }
  714. });
  715. // Build a list of top level rows with attached children
  716. topLevelRows = [];
  717. allTableRows.forEach(function(row) {
  718. // Add us to top level if we are a top-level row...
  719. if (row.hasNoURL) {
  720. topLevelRows.push(row);
  721. // Add a duplicate child row that holds the graph for the parent
  722. var graphHolder = new TableRow(runs, row.test, referenceIndex, useLargeLinePlots);
  723. graphHolder.isImportant = true;
  724. graphHolder.URL = 'Summary';
  725. graphHolder.hideRowData();
  726. allTableRows.push(graphHolder);
  727. row.addNestedChild(graphHolder);
  728. return;
  729. }
  730. // ...or add us to our parent if we have one ...
  731. for (var i = 0; i < allTableRows.length; i++) {
  732. if (allTableRows[i].isParentOf(row)) {
  733. allTableRows[i].addNestedChild(row);
  734. return;
  735. }
  736. }
  737. // ...otherwise this result is orphaned, display it at top level with a graph
  738. row.hasGraph = true;
  739. topLevelRows.push(row);
  740. });
  741. buildTable(topLevelRows);
  742. $('.closeButton').click(function(event) {
  743. for (var i = 0; i < runs.length; i++) {
  744. if (runs[i].id() == event.target.parentNode.id) {
  745. runs[i].hide();
  746. undeleteManager.ondelete(runs[i].id());
  747. location.reload();
  748. break;
  749. }
  750. }
  751. event.stopPropagation();
  752. });
  753. $('.closeAllButton').click(function(event) {
  754. for (var i = 0; i < allTableRows.length; i++) {
  755. allTableRows[i].closeRow();
  756. }
  757. event.stopPropagation();
  758. });
  759. $('.openAllButton').click(function(event) {
  760. for (var i = 0; i < topLevelRows.length; i++) {
  761. topLevelRows[i].openRow();
  762. }
  763. event.stopPropagation();
  764. });
  765. setUpSortClicks(runs);
  766. $('.label').click(function(event) {
  767. for (var i = 0; i < runs.length; i++) {
  768. if (runs[i].id() == event.target.parentNode.id) {
  769. $(event.target).replaceWith('<input id="labelEditor" type="text" value="' + runs[i].label() + '">');
  770. $('#labelEditor').focusout(function() {
  771. runs[i].setLabel(this.value);
  772. location.reload();
  773. });
  774. $('#labelEditor').keypress(function(event) {
  775. if (event.which == 13) {
  776. runs[i].setLabel(this.value);
  777. location.reload();
  778. }
  779. });
  780. $('#labelEditor').click(function(event) {
  781. event.stopPropagation();
  782. });
  783. $('#labelEditor').mousedown(function(event) {
  784. event.stopPropagation();
  785. });
  786. $('#labelEditor').select();
  787. break;
  788. }
  789. }
  790. event.stopPropagation();
  791. });
  792. }
  793. function validForSorting(row) {
  794. return ($.type(row.sortValue) === 'string') || !isNaN(row.sortValue);
  795. }
  796. var sortDirection = 1;
  797. function sortRows(rows) {
  798. rows.sort(
  799. function(rowA,rowB) {
  800. if (validForSorting(rowA) !== validForSorting(rowB)) {
  801. // Sort valid values upwards when compared to invalid
  802. if (validForSorting(rowA)) {
  803. return -1;
  804. }
  805. if (validForSorting(rowB)) {
  806. return 1;
  807. }
  808. }
  809. // Some rows always sort to the top
  810. if (rowA.isImportant) {
  811. return -1;
  812. }
  813. if (rowB.isImportant) {
  814. return 1;
  815. }
  816. if (rowA.sortValue === rowB.sortValue) {
  817. // Sort identical values by name to keep the sort stable,
  818. // always keep name alphabetical (even if a & b sort values
  819. // are invalid)
  820. return rowA.test.name() > rowB.test.name() ? 1 : -1;
  821. }
  822. return rowA.sortValue > rowB.sortValue ? sortDirection : -sortDirection;
  823. } );
  824. // Sort the rows' children
  825. rows.forEach(function(row) {
  826. sortRows(row.children);
  827. });
  828. }
  829. function buildTable(rows) {
  830. rows.forEach(function(row) {
  831. row.removeFromPage();
  832. });
  833. sortRows(rows);
  834. rows.forEach(function(row) {
  835. row.addToPage();
  836. });
  837. }
  838. var activeSortHeaderElement = undefined;
  839. var columnSortDirection = {};
  840. function determineColumnSortDirection(element) {
  841. columnDirection = columnSortDirection[element.id];
  842. if (columnDirection === undefined) {
  843. // First time we've sorted this row, default to down
  844. columnSortDirection[element.id] = SORT_DOWN_CLASS;
  845. } else if (element === activeSortHeaderElement) {
  846. // Clicking on same header again, swap direction
  847. columnSortDirection[element.id] = (columnDirection === SORT_UP_CLASS) ? SORT_DOWN_CLASS : SORT_UP_CLASS;
  848. }
  849. }
  850. function updateSortDirection(element) {
  851. // Remove old header's sort arrow
  852. if (activeSortHeaderElement !== undefined) {
  853. activeSortHeaderElement.classList.remove(columnSortDirection[activeSortHeaderElement.id]);
  854. }
  855. determineColumnSortDirection(element);
  856. sortDirection = (columnSortDirection[element.id] === SORT_UP_CLASS) ? 1 : -1;
  857. // Add new header's sort arrow
  858. element.classList.add(columnSortDirection[element.id]);
  859. activeSortHeaderElement = element;
  860. }
  861. function sortByName(event) {
  862. updateSortDirection(event.toElement);
  863. allTableRows.forEach(function(row) {
  864. row.prepareToSortByName();
  865. });
  866. buildTable(topLevelRows);
  867. }
  868. function sortByUnit(event) {
  869. updateSortDirection(event.toElement);
  870. allTableRows.forEach(function(row) {
  871. row.prepareToSortByUnit();
  872. });
  873. buildTable(topLevelRows);
  874. }
  875. function sortByResult(event) {
  876. updateSortDirection(event.toElement);
  877. var runId = event.target.id;
  878. allTableRows.forEach(function(row) {
  879. row.prepareToSortByTestResults(runId);
  880. });
  881. buildTable(topLevelRows);
  882. }
  883. function sortByReference(event) {
  884. updateSortDirection(event.toElement);
  885. // The element ID has _compare appended to allow us to set up a click event
  886. // remove the _compare to return a useful Id
  887. var runIdWithCompare = event.target.id;
  888. var runId = runIdWithCompare.split('_')[0];
  889. allTableRows.forEach(function(row) {
  890. row.prepareToSortRelativeToReference(runId);
  891. });
  892. buildTable(topLevelRows);
  893. }
  894. function linearRegression(points) {
  895. // Implement http://www.easycalculation.com/statistics/learn-correlation.php.
  896. // x = magnitude
  897. // y = iterations
  898. var sumX = 0;
  899. var sumY = 0;
  900. var sumXSquared = 0;
  901. var sumYSquared = 0;
  902. var sumXTimesY = 0;
  903. for (var i = 0; i < points.length; i++) {
  904. var x = i;
  905. var y = points[i];
  906. sumX += x;
  907. sumY += y;
  908. sumXSquared += x * x;
  909. sumYSquared += y * y;
  910. sumXTimesY += x * y;
  911. }
  912. var r = (points.length * sumXTimesY - sumX * sumY) /
  913. Math.sqrt((points.length * sumXSquared - sumX * sumX) *
  914. (points.length * sumYSquared - sumY * sumY));
  915. if (isNaN(r) || r == Math.Infinity)
  916. r = 0;
  917. var slope = (points.length * sumXTimesY - sumX * sumY) / (points.length * sumXSquared - sumX * sumX);
  918. var intercept = sumY / points.length - slope * sumX / points.length;
  919. return {slope: slope, intercept: intercept, rSquared: r * r};
  920. }
  921. var warningSign = '<svg viewBox="0 0 100 100" style="width: 18px; height: 18px; vertical-align: bottom;" version="1.1">'
  922. + '<polygon fill="red" points="50,10 90,80 10,80 50,10" stroke="red" stroke-width="10" stroke-linejoin="round" />'
  923. + '<polygon fill="white" points="47,30 48,29, 50, 28.7, 52,29 53,30 50,60" stroke="white" stroke-width="10" stroke-linejoin="round" />'
  924. + '<circle cx="50" cy="73" r="6" fill="white" />'
  925. + '</svg>';
  926. function TableRow(runs, test, referenceIndex, useLargeLinePlots) {
  927. this.runs = runs;
  928. this.test = test;
  929. this.referenceIndex = referenceIndex;
  930. this.useLargeLinePlots = useLargeLinePlots;
  931. this.children = [];
  932. this.tableRow = $('<tr class="highlight">' +
  933. '<td class="test collapsed" >' +
  934. this.test.name() +
  935. '</td>' +
  936. '<td class="unit">' +
  937. this.test.unit() +
  938. '</td>' +
  939. '</tr>');
  940. var runIndex = 0;
  941. var results = this.test.results();
  942. var referenceResult = undefined;
  943. this.resultIndexMap = {};
  944. for (var i = 0; i < results.length; i++) {
  945. while (this.runs[runIndex] !== results[i].run())
  946. runIndex++;
  947. if (runIndex === this.referenceIndex)
  948. referenceResult = results[i];
  949. this.resultIndexMap[runIndex] = i;
  950. }
  951. for (var i = 0; i < this.runs.length; i++) {
  952. var resultIndex = this.resultIndexMap[i];
  953. if (resultIndex === undefined)
  954. this.tableRow.append(this.markupForMissingRun(i == this.referenceIndex));
  955. else
  956. this.tableRow.append(this.markupForRun(results[resultIndex], referenceResult));
  957. }
  958. // Use the test name (without URL) to bind parents and their children
  959. var nameAndURL = this.test.name().split('.');
  960. var benchmarkName = nameAndURL.shift();
  961. this.testName = nameAndURL.shift();
  962. this.hasNoURL = (nameAndURL.length === 0);
  963. if (!this.hasNoURL) {
  964. // Re-join the URL
  965. this.URL = nameAndURL.join('.');
  966. }
  967. this.isImportant = false;
  968. this.hasGraph = false;
  969. this.currentIndentationClass = ''
  970. this.indentLevel = 0;
  971. this.setRowNestedState(COLLAPSED);
  972. this.setVisibility(VISIBLE);
  973. this.prepareToSortByName();
  974. }
  975. TableRow.prototype.hideRowData = function() {
  976. data = this.tableRow.children('td');
  977. for (index in data) {
  978. if (index > 0) {
  979. // Blank out everything except the test name
  980. data[index].innerHTML = '';
  981. }
  982. }
  983. }
  984. TableRow.prototype.prepareToSortByTestResults = function(runId) {
  985. var testResults = this.test.results();
  986. // Find the column in this row that matches the runId and prepare to
  987. // sort by the mean of that test.
  988. for (index in testResults) {
  989. sourceId = testResults[index].run().id();
  990. if (runId === sourceId) {
  991. this.sortValue = testResults[index].mean();
  992. return;
  993. }
  994. }
  995. // This row doesn't have any results for the passed runId
  996. this.sortValue = undefined;
  997. }
  998. TableRow.prototype.prepareToSortRelativeToReference = function(runId) {
  999. var testResults = this.test.results();
  1000. // Get index of test results that correspond to the reference column.
  1001. var remappedReferenceIndex = this.resultIndexMap[this.referenceIndex];
  1002. if (remappedReferenceIndex === undefined) {
  1003. // This test has no results in the reference run.
  1004. this.sortValue = undefined;
  1005. return;
  1006. }
  1007. otherResults = testResults[remappedReferenceIndex];
  1008. // Find the column in this row that matches the runId and prepare to
  1009. // sort by the difference from the reference.
  1010. for (index in testResults) {
  1011. sourceId = testResults[index].run().id();
  1012. if (runId === sourceId) {
  1013. this.sortValue = testResults[index].percentDifference(otherResults);
  1014. if (this.test.biggerIsBetter()) {
  1015. // For this test bigger is not better
  1016. this.sortValue = -this.sortValue;
  1017. }
  1018. return;
  1019. }
  1020. }
  1021. // This row doesn't have any results for the passed runId
  1022. this.sortValue = undefined;
  1023. }
  1024. TableRow.prototype.prepareToSortByUnit = function() {
  1025. this.sortValue = this.test.unit().toLowerCase();
  1026. }
  1027. TableRow.prototype.prepareToSortByName = function() {
  1028. this.sortValue = this.test.name().toLowerCase();
  1029. }
  1030. TableRow.prototype.isParentOf = function(row) {
  1031. return this.hasNoURL && (this.testName === row.testName);
  1032. }
  1033. TableRow.prototype.addNestedChild = function(child) {
  1034. this.children.push(child);
  1035. // Indent child one step in from parent
  1036. child.indentLevel = this.indentLevel + INDENTATION;
  1037. child.hasGraph = true;
  1038. // Start child off as hidden (i.e. collapsed inside parent)
  1039. child.setVisibility(INVISIBLE);
  1040. child.updateIndentation();
  1041. // Show URL in the title column
  1042. child.tableRow.children()[0].innerHTML = child.URL;
  1043. // Set up class to change background colour of nested rows
  1044. if (child.isImportant) {
  1045. child.tableRow.addClass('importantNestedRow');
  1046. } else {
  1047. child.tableRow.addClass('nestedRow');
  1048. }
  1049. }
  1050. TableRow.prototype.setVisibility = function(visibility) {
  1051. this.visibility = visibility;
  1052. this.tableRow[0].style.display = (visibility === INVISIBLE) ? 'none' : '';
  1053. }
  1054. TableRow.prototype.setRowNestedState = function(newState) {
  1055. this.rowState = newState;
  1056. this.updateIndentation();
  1057. }
  1058. TableRow.prototype.updateIndentation = function() {
  1059. var element = this.tableRow.children('td').first();
  1060. element.removeClass(this.currentIndentationClass);
  1061. this.currentIndentationClass = (this.rowState === COLLAPSED) ? 'collapsed' : 'expanded';
  1062. element[0].style.marginLeft = this.indentLevel.toString() + 'px';
  1063. element[0].style.float = 'left';
  1064. element.addClass(this.currentIndentationClass);
  1065. }
  1066. TableRow.prototype.addToPage = function() {
  1067. $('#container').children('tbody').last().append(this.tableRow);
  1068. // Set up click callback
  1069. var owningObject = this;
  1070. this.tableRow.click(function(event) {
  1071. event.preventDefault();
  1072. owningObject.toggle();
  1073. });
  1074. // Add children to the page too
  1075. this.children.forEach(function(child) {
  1076. child.addToPage();
  1077. });
  1078. }
  1079. TableRow.prototype.removeFromPage = function() {
  1080. // Remove children
  1081. this.children.forEach(function(child) {
  1082. child.removeFromPage();
  1083. });
  1084. // Remove us
  1085. this.tableRow.remove();
  1086. }
  1087. TableRow.prototype.markupForRun = function(result, referenceResult) {
  1088. var comparisonCell = '';
  1089. var shouldCompare = result !== referenceResult;
  1090. if (shouldCompare) {
  1091. var comparisonText = '';
  1092. var className = '';
  1093. if (referenceResult) {
  1094. var percentDifference = referenceResult.percentDifference(result);
  1095. if (isNaN(percentDifference)) {
  1096. comparisonText = 'Unknown';
  1097. className = UNKNOWN_CLASS;
  1098. } else if (Math.abs(percentDifference) < SMALLEST_PERCENT_DISPLAYED) {
  1099. comparisonText = 'Equal';
  1100. // Show equal values in green
  1101. className = BETTER_CLASS;
  1102. } else {
  1103. var better = this.test.biggerIsBetter() ? percentDifference > 0 : percentDifference < 0;
  1104. comparisonText = formatPercentage(Math.abs(percentDifference)) + (better ? ' Better' : ' Worse');
  1105. className = better ? BETTER_CLASS : WORSE_CLASS;
  1106. }
  1107. if (!referenceResult.isStatisticallySignificant(result)) {
  1108. // Put result in brackets and fade if not statistically significant
  1109. className += ' fadeOut';
  1110. comparisonText = '(' + comparisonText + ')';
  1111. }
  1112. }
  1113. comparisonCell = '<td class="comparison ' + className + '">' + comparisonText + '</td>';
  1114. }
  1115. var values = result.values();
  1116. var warning = '';
  1117. var regressionAnalysis = '';
  1118. if (result.histogramValues) {
  1119. // Don't calculate regression result for histograms.
  1120. } else if (values && values.length > 3) {
  1121. regressionResult = linearRegression(values);
  1122. regressionAnalysis = 'slope=' + toFixedWidthPrecision(regressionResult.slope)
  1123. + ', R^2=' + toFixedWidthPrecision(regressionResult.rSquared);
  1124. if (regressionResult.rSquared > 0.6 && Math.abs(regressionResult.slope) > 0.01) {
  1125. warning = ' <span class="regression-warning" title="Detected a time dependency with ' + regressionAnalysis + '">' + warningSign + ' </span>';
  1126. }
  1127. }
  1128. var referenceClass = shouldCompare ? '' : 'reference';
  1129. var statistics = '&sigma;=' + toFixedWidthPrecision(result.confidenceIntervalDelta()) + ', min=' + toFixedWidthPrecision(result.min())
  1130. + ', max=' + toFixedWidthPrecision(result.max()) + '\n' + regressionAnalysis;
  1131. var confidence;
  1132. if (isNaN(result.confidenceIntervalDeltaRatio())) {
  1133. // Don't bother showing +- Nan as it is meaningless
  1134. confidence = '';
  1135. } else {
  1136. confidence = '&plusmn; ' + formatPercentage(result.confidenceIntervalDeltaRatio());
  1137. }
  1138. return '<td class="result ' + referenceClass + '" title="' + statistics + '">' + toFixedWidthPrecision(result.mean())
  1139. + '</td><td class="confidenceIntervalDelta ' + referenceClass + '" title="' + statistics + '">' + confidence + warning + '</td>' + comparisonCell;
  1140. }
  1141. TableRow.prototype.markupForMissingRun = function(isReference) {
  1142. if (isReference) {
  1143. return '<td colspan=2 class="missingReference">Missing</td>';
  1144. }
  1145. return '<td colspan=3 class="missing">Missing</td>';
  1146. }
  1147. TableRow.prototype.openRow = function() {
  1148. if (this.rowState === EXPANDED) {
  1149. // If we're already expanded, open our children instead
  1150. this.children.forEach(function(child) {
  1151. child.openRow();
  1152. });
  1153. return;
  1154. }
  1155. this.setRowNestedState(EXPANDED);
  1156. if (this.hasGraph) {
  1157. var firstCell = this.tableRow.children('td').first();
  1158. var plot = createPlot(firstCell, this.test, this.useLargeLinePlots);
  1159. plot.css({'position': 'absolute', 'z-index': 2});
  1160. var offset = this.tableRow.offset();
  1161. offset.left += GRAPH_INDENT;
  1162. offset.top += this.tableRow.outerHeight();
  1163. plot.offset(offset);
  1164. this.tableRow.children('td').css({'padding-bottom': plot.outerHeight() + PADDING_UNDER_GRAPH});
  1165. }
  1166. this.children.forEach(function(child) {
  1167. child.setVisibility(VISIBLE);
  1168. });
  1169. if (this.children.length === 1) {
  1170. // If we only have a single child...
  1171. var child = this.children[0];
  1172. if (child.isImportant) {
  1173. // ... and it is important (i.e. the summary row) just open it when
  1174. // parent is opened to save needless clicking
  1175. child.openRow();
  1176. }
  1177. }
  1178. }
  1179. TableRow.prototype.closeRow = function() {
  1180. if (this.rowState === COLLAPSED) {
  1181. return;
  1182. }
  1183. this.setRowNestedState(COLLAPSED);
  1184. if (this.hasGraph) {
  1185. var firstCell = this.tableRow.children('td').first();
  1186. firstCell.children('section').remove();
  1187. this.tableRow.children('td').css({'padding-bottom': ''});
  1188. }
  1189. this.children.forEach(function(child) {
  1190. // Make children invisible, but leave their collapsed status alone
  1191. child.setVisibility(INVISIBLE);
  1192. });
  1193. }
  1194. TableRow.prototype.toggle = function() {
  1195. if (this.rowState === EXPANDED) {
  1196. this.closeRow();
  1197. } else {
  1198. this.openRow();
  1199. }
  1200. return false;
  1201. }
  1202. function init() {
  1203. var runs = [];
  1204. var metrics = {};
  1205. var deletedRunsById = {};
  1206. $.each(JSON.parse(document.getElementById('results-json').textContent), function(index, entry) {
  1207. var run = new TestRun(entry);
  1208. if (run.isHidden()) {
  1209. deletedRunsById[run.id()] = run;
  1210. return;
  1211. }
  1212. runs.push(run);
  1213. function addTests(tests) {
  1214. for (var testName in tests) {
  1215. var rawMetrics = tests[testName].metrics;
  1216. for (var metricName in rawMetrics) {
  1217. var fullMetricName = testName + ':' + metricName;
  1218. var metric = metrics[fullMetricName];
  1219. if (!metric) {
  1220. metric = new PerfTestMetric(testName, metricName, rawMetrics[metricName].units, rawMetrics[metricName].important);
  1221. metrics[fullMetricName] = metric;
  1222. }
  1223. // std & degrees_of_freedom could be undefined
  1224. metric.addResult(
  1225. new TestResult(metric, rawMetrics[metricName].current,
  1226. run, rawMetrics[metricName]['std'], rawMetrics[metricName]['degrees_of_freedom']));
  1227. }
  1228. }
  1229. }
  1230. addTests(entry.tests);
  1231. });
  1232. var useLargeLinePlots = false;
  1233. var referenceIndex = 0;
  1234. var testTypeSelector = new TestTypeSelector(metrics);
  1235. var buttonHTML = testTypeSelector.buildButtonHTMLForUsedTestTypes();
  1236. $('#time-memory').append(buttonHTML);
  1237. $('#scatter-line').bind('change', function(event, checkedElement) {
  1238. useLargeLinePlots = checkedElement.textContent == 'Line';
  1239. displayTable(metrics, runs, testTypeSelector, referenceIndex, useLargeLinePlots);
  1240. });
  1241. runs.map(function(run, index) {
  1242. $('#reference').append('<span value="' + index + '"' + (index == referenceIndex ? ' class="checked"' : '') + ' title="' + run.description() + '">' + run.label() + '</span>');
  1243. })
  1244. $('#time-memory').bind('change', function(event, checkedElement) {
  1245. testTypeSelector.testTypeName = checkedElement.textContent;
  1246. displayTable(metrics, runs, testTypeSelector, referenceIndex, useLargeLinePlots);
  1247. });
  1248. $('#reference').bind('change', function(event, checkedElement) {
  1249. referenceIndex = parseInt(checkedElement.getAttribute('value'));
  1250. displayTable(metrics, runs, testTypeSelector, referenceIndex, useLargeLinePlots);
  1251. });
  1252. displayTable(metrics, runs, testTypeSelector, referenceIndex, useLargeLinePlots);
  1253. $('.checkbox').each(function(index, checkbox) {
  1254. $(checkbox).children('span').click(function(event) {
  1255. if ($(this).hasClass('checked'))
  1256. return;
  1257. $(checkbox).children('span').removeClass('checked');
  1258. $(this).addClass('checked');
  1259. $(checkbox).trigger('change', $(this));
  1260. });
  1261. });
  1262. runToUndelete = deletedRunsById[undeleteManager.mostRecentlyDeletedId()];
  1263. if (runToUndelete) {
  1264. $('#undelete').html('Undelete ' + runToUndelete.label());
  1265. $('#undelete').attr('title', runToUndelete.description());
  1266. $('#undelete').click(function(event) {
  1267. runToUndelete.show();
  1268. undeleteManager.undeleteMostRecent();
  1269. location.reload();
  1270. });
  1271. } else {
  1272. $('#undelete').hide();
  1273. }
  1274. }
  1275. </script>
  1276. <script id="results-json" type="application/json">%json_results%</script>
  1277. <script id="units-json" type="application/json">%json_units%</script>
  1278. </body>
  1279. </html>