Title: [128779] trunk/PerformanceTests
Revision
128779
Author
[email protected]
Date
2012-09-17 11:17:10 -0700 (Mon, 17 Sep 2012)

Log Message

Perf test results is incomprehensive
https://bugs.webkit.org/show_bug.cgi?id=94668

Reviewed by Eric Seidel.

Overhauled the results page to have a tabular view. Clicking on each row shows a flot graph we used to have.
For each run and test, we show the mean value with the standard deviation along with the percent difference
against the reference run chosen by the user if the difference is statistically significant; it also indicates
whether the new value is progression or not.

The unit of each test is adjusted automatically using SI prefixes (Kilo, Mega, Milli), and rows can be sorted
by each column. Time and memory results are separated into two tabs.

* resources/jquery.tablesorter.min.js: Added.
* resources/results-template.html:

Modified Paths

Added Paths

Diff

Modified: trunk/PerformanceTests/ChangeLog (128778 => 128779)


--- trunk/PerformanceTests/ChangeLog	2012-09-17 18:13:04 UTC (rev 128778)
+++ trunk/PerformanceTests/ChangeLog	2012-09-17 18:17:10 UTC (rev 128779)
@@ -1,3 +1,21 @@
+2012-09-17  Ryosuke Niwa  <[email protected]>
+
+        Perf test results is incomprehensive
+        https://bugs.webkit.org/show_bug.cgi?id=94668
+
+        Reviewed by Eric Seidel.
+
+        Overhauled the results page to have a tabular view. Clicking on each row shows a flot graph we used to have.
+        For each run and test, we show the mean value with the standard deviation along with the percent difference
+        against the reference run chosen by the user if the difference is statistically significant; it also indicates
+        whether the new value is progression or not.
+
+        The unit of each test is adjusted automatically using SI prefixes (Kilo, Mega, Milli), and rows can be sorted
+        by each column. Time and memory results are separated into two tabs.
+
+        * resources/jquery.tablesorter.min.js: Added.
+        * resources/results-template.html:
+
 2012-09-14  Ryosuke Niwa  <[email protected]>
 
         Use performance.webkitNow in PerfTestRunner

Added: trunk/PerformanceTests/resources/jquery.tablesorter.min.js (0 => 128779)


--- trunk/PerformanceTests/resources/jquery.tablesorter.min.js	                        (rev 0)
+++ trunk/PerformanceTests/resources/jquery.tablesorter.min.js	2012-09-17 18:17:10 UTC (rev 128779)
@@ -0,0 +1,4 @@
+
+(function($){$.extend({tablesorter:new
+function(){var parsers=[],widgets=[];this.defaults={cssHeader:"header",cssAsc:"headerSortUp",cssDesc:"headerSortDown",cssChildRow:"expand-child",sortInitialOrder:"asc",sortMultiSortKey:"shiftKey",sortForce:null,sortAppend:null,sortLocaleCompare:true,textExtraction:"simple",parsers:{},widgets:[],widgetZebra:{css:["even","odd"]},headers:{},widthFixed:false,cancelSelection:true,sortList:[],headerList:[],dateFormat:"us",decimal:'/\.|\,/g',onRenderHeader:null,selectorHeaders:'thead th',debug:false};function benchmark(s,d){log(s+","+(new Date().getTime()-d.getTime())+"ms");}this.benchmark=benchmark;function log(s){if(typeof console!="undefined"&&typeof console.debug!="undefined"){console.log(s);}else{alert(s);}}function buildParserCache(table,$headers){if(table.config.debug){var parsersDebug="";}if(table.tBodies.length==0)return;var rows=table.tBodies[0].rows;if(rows[0]){var list=[],cells=rows[0].cells,l=cells.length;for(var i=0;i<l;i++){var p=false;if($.metadata&&($($headers[i]).metadata()&&$($headers[i]).metadata().sorter)){p=getParserById($($headers[i]).metadata().sorter);}else if((table.config.headers[i]&&table.config.headers[i].sorter)){p=getParserById(table.config.headers[i].sorter);}if(!p){p=detectParserForColumn(table,rows,-1,i);}if(table.config.debug){parsersDebug+="column:"+i+" parser:"+p.id+"\n";}list.push(p);}}if(table.config.debug){log(parsersDebug);}return list;};function detectParserForColumn(table,rows,rowIndex,cellIndex){var l=parsers.length,node=false,nodeValue=false,keepLooking=true;while(nodeValue==''&&keepLooking){rowIndex++;if(rows[rowIndex]){node=getNodeFromRowAndCellIndex(rows,rowIndex,cellIndex);nodeValue=trimAndGetNodeText(table.config,node);if(table.config.debug){log('Checking if value was empty on row:'+rowIndex);}}else{keepLooking=false;}}for(var i=1;i<l;i++){if(parsers[i].is(nodeValue,table,node)){return parsers[i];}}return parsers[0];}function getNodeFromRowAndCellIndex(rows,rowIndex,cellIndex){return rows[rowIndex].cells[cellIndex];}function trimAndGetNodeText(config,node){return $.trim(getElementText(config,node));}function getParserById(name){var l=parsers.length;for(var i=0;i<l;i++){if(parsers[i].id.toLowerCase()==name.toLowerCase()){return parsers[i];}}return false;}function buildCache(table){if(table.config.debug){var cacheTime=new Date();}var totalRows=(table.tBodies[0]&&table.tBodies[0].rows.length)||0,totalCells=(table.tBodies[0].rows[0]&&table.tBodies[0].rows[0].cells.length)||0,parsers=table.config.parsers,cache={row:[],normalized:[]};for(var i=0;i<totalRows;++i){var c=$(table.tBodies[0].rows[i]),cols=[];if(c.hasClass(table.config.cssChildRow)){cache.row[cache.row.length-1]=cache.row[cache.row.length-1].add(c);continue;}cache.row.push(c);for(var j=0;j<totalCells;++j){cols.push(parsers[j].format(getElementText(table.config,c[0].cells[j]),table,c[0].cells[j]));}cols.push(cache.normalized.length);cache.normalized.push(cols);cols=null;};if(table.config.debug){benchmark("Building cache for "+totalRows+" rows:",cacheTime);}return cache;};function getElementText(config,node){var text="";if(!node)return"";if(!config.supportsTextContent)config.supportsTextContent=node.textContent||false;if(config.textExtraction=="simple"){if(config.supportsTextContent){text=node.textContent;}else{if(node.childNodes[0]&&node.childNodes[0].hasChildNodes()){text=node.childNodes[0].innerHTML;}else{text=node.innerHTML;}}}else{if(typeof(config.textExtraction)=="function"){text=config.textExtraction(node);}else{text=$(node).text();}}return text;}function appendToTable(table,cache){if(table.config.debug){var appendTime=new Date()}var c=cache,r=c.row,n=c.normalized,totalRows=n.length,checkCell=(n[0].length-1),tableBody=$(table.tBodies[0]),rows=[];for(var i=0;i<totalRows;i++){var pos=n[i][checkCell];rows.push(r[pos]);if(!table.config.appender){var l=r[pos].length;for(var j=0;j<l;j++){tableBody[0].appendChild(r[pos][j]);}}}if(table.config.appender){table.config.appender(table,rows);}rows=null;if(table.config.debug){benchmark("Rebuilt table:",appendTime);}applyWidget(table);setTimeout(function(){$(table).trigger("sortEnd");},0);};function buildHeaders(table){if(table.config.debug){var time=new Date();}var meta=($.metadata)?true:false;var header_index=computeTableHeaderCellIndexes(table);$tableHeaders=$(table.config.selectorHeaders,table).each(function(index){this.column=header_index[this.parentNode.rowIndex+"-"+this.cellIndex];this.order=formatSortingOrder(table.config.sortInitialOrder);this.count=this.order;if(checkHeaderMetadata(this)||checkHeaderOptions(table,index))this.sortDisabled=true;if(checkHeaderOptionsSortingLocked(table,index))this.order=this.lockedOrder=checkHeaderOptionsSortingLocked(table,index);if(!this.sortDisabled){var $th=$(this).addClass(table.config.cssHeader);if(table.config.onRenderHeader)table.config.onRenderHeader.apply($th);}table.config.headerList[index]=this;});if(table.config.debug){benchmark("Built headers:",time);log($tableHeaders);}return $tableHeaders;};function computeTableHeaderCellIndexes(t){var matrix=[];var lookup={};var thead=t.getElementsByTagName('THEAD')[0];var trs=thead.getElementsByTagName('TR');for(var i=0;i<trs.length;i++){var cells=trs[i].cells;for(var j=0;j<cells.length;j++){var c=cells[j];var rowIndex=c.parentNode.rowIndex;var cellId=rowIndex+"-"+c.cellIndex;var rowSpan=c.rowSpan||1;var colSpan=c.colSpan||1
+var firstAvailCol;if(typeof(matrix[rowIndex])=="undefined"){matrix[rowIndex]=[];}for(var k=0;k<matrix[rowIndex].length+1;k++){if(typeof(matrix[rowIndex][k])=="undefined"){firstAvailCol=k;break;}}lookup[cellId]=firstAvailCol;for(var k=rowIndex;k<rowIndex+rowSpan;k++){if(typeof(matrix[k])=="undefined"){matrix[k]=[];}var matrixrow=matrix[k];for(var l=firstAvailCol;l<firstAvailCol+colSpan;l++){matrixrow[l]="x";}}}}return lookup;}function checkCellColSpan(table,rows,row){var arr=[],r=table.tHead.rows,c=r[row].cells;for(var i=0;i<c.length;i++){var cell=c[i];if(cell.colSpan>1){arr=arr.concat(checkCellColSpan(table,headerArr,row++));}else{if(table.tHead.length==1||(cell.rowSpan>1||!r[row+1])){arr.push(cell);}}}return arr;};function checkHeaderMetadata(cell){if(($.metadata)&&($(cell).metadata().sorter===false)){return true;};return false;}function checkHeaderOptions(table,i){if((table.config.headers[i])&&(table.config.headers[i].sorter===false)){return true;};return false;}function checkHeaderOptionsSortingLocked(table,i){if((table.config.headers[i])&&(table.config.headers[i].lockedOrder))return table.config.headers[i].lockedOrder;return false;}function applyWidget(table){var c=table.config.widgets;var l=c.length;for(var i=0;i<l;i++){getWidgetById(c[i]).format(table);}}function getWidgetById(name){var l=widgets.length;for(var i=0;i<l;i++){if(widgets[i].id.toLowerCase()==name.toLowerCase()){return widgets[i];}}};function formatSortingOrder(v){if(typeof(v)!="Number"){return(v.toLowerCase()=="desc")?1:0;}else{return(v==1)?1:0;}}function isValueInArray(v,a){var l=a.length;for(var i=0;i<l;i++){if(a[i][0]==v){return true;}}return false;}function setHeadersCss(table,$headers,list,css){$headers.removeClass(css[0]).removeClass(css[1]);var h=[];$headers.each(function(offset){if(!this.sortDisabled){h[this.column]=$(this);}});var l=list.length;for(var i=0;i<l;i++){h[list[i][0]].addClass(css[list[i][1]]);}}function fixColumnWidth(table,$headers){var c=table.config;if(c.widthFixed){var colgroup=$('<colgroup>');$("tr:first td",table.tBodies[0]).each(function(){colgroup.append($('<col>').css('width',$(this).width()));});$(table).prepend(colgroup);};}function updateHeaderSortCount(table,sortList){var c=table.config,l=sortList.length;for(var i=0;i<l;i++){var s=sortList[i],o=c.headerList[s[0]];o.count=s[1];o.count++;}}function multisort(table,sortList,cache){if(table.config.debug){var sortTime=new Date();}var dynamicExp="var sortWrapper = function(a,b) {",l=sortList.length;for(var i=0;i<l;i++){var c=sortList[i][0];var order=sortList[i][1];var s=(table.config.parsers[c].type=="text")?((order==0)?makeSortFunction("text","asc",c):makeSortFunction("text","desc",c)):((order==0)?makeSortFunction("numeric","asc",c):makeSortFunction("numeric","desc",c));var e="e"+i;dynamicExp+="var "+e+" = "+s;dynamicExp+="if("+e+") { return "+e+"; } ";dynamicExp+="else { ";}var orgOrderCol=cache.normalized[0].length-1;dynamicExp+="return a["+orgOrderCol+"]-b["+orgOrderCol+"];";for(var i=0;i<l;i++){dynamicExp+="}; ";}dynamicExp+="return 0; ";dynamicExp+="}; ";if(table.config.debug){benchmark("Evaling _expression_:"+dynamicExp,new Date());}eval(dynamicExp);cache.normalized.sort(sortWrapper);if(table.config.debug){benchmark("Sorting on "+sortList.toString()+" and dir "+order+" time:",sortTime);}return cache;};function makeSortFunction(type,direction,index){var a="a["+index+"]",b="b["+index+"]";if(type=='text'&&direction=='asc'){return"("+a+" == "+b+" ? 0 : ("+a+" === null ? Number.POSITIVE_INFINITY : ("+b+" === null ? Number.NEGATIVE_INFINITY : ("+a+" < "+b+") ? -1 : 1 )));";}else if(type=='text'&&direction=='desc'){return"("+a+" == "+b+" ? 0 : ("+a+" === null ? Number.POSITIVE_INFINITY : ("+b+" === null ? Number.NEGATIVE_INFINITY : ("+b+" < "+a+") ? -1 : 1 )));";}else if(type=='numeric'&&direction=='asc'){return"("+a+" === null && "+b+" === null) ? 0 :("+a+" === null ? Number.POSITIVE_INFINITY : ("+b+" === null ? Number.NEGATIVE_INFINITY : "+a+" - "+b+"));";}else if(type=='numeric'&&direction=='desc'){return"("+a+" === null && "+b+" === null) ? 0 :("+a+" === null ? Number.POSITIVE_INFINITY : ("+b+" === null ? Number.NEGATIVE_INFINITY : "+b+" - "+a+"));";}};function makeSortText(i){return"((a["+i+"] < b["+i+"]) ? -1 : ((a["+i+"] > b["+i+"]) ? 1 : 0));";};function makeSortTextDesc(i){return"((b["+i+"] < a["+i+"]) ? -1 : ((b["+i+"] > a["+i+"]) ? 1 : 0));";};function makeSortNumeric(i){return"a["+i+"]-b["+i+"];";};function makeSortNumericDesc(i){return"b["+i+"]-a["+i+"];";};function sortText(a,b){if(table.config.sortLocaleCompare)return a.localeCompare(b);return((a<b)?-1:((a>b)?1:0));};function sortTextDesc(a,b){if(table.config.sortLocaleCompare)return b.localeCompare(a);return((b<a)?-1:((b>a)?1:0));};function sortNumeric(a,b){return a-b;};function sortNumericDesc(a,b){return b-a;};function getCachedSortType(parsers,i){return parsers[i].type;};this.construct=function(settings){return this.each(function(){if(!this.tHead||!this.tBodies)return;var $this,$document,$headers,cache,config,shiftDown=0,sortOrder;this.config={};config=$.extend(this.config,$.tablesorter.defaults,settings);$this=$(this);$.data(this,"tablesorter",config);$headers=buildHeaders(this);this.config.parsers=buildParserCache(this,$headers);cache=buildCache(this);var sortCSS=[config.cssDesc,config.cssAsc];fixColumnWidth(this);$headers.click(function(e){var totalRows=($this[0].tBodies[0]&&$this[0].tBodies[0].rows.length)||0;if(!this.sortDisabled&&totalRows>0){$this.trigger("sortStart");var $cell=$(this);var i=this.column;this.order=this.count++%2;if(this.lockedOrder)this.order=this.lockedOrder;if(!e[config.sortMultiSortKey]){config.sortList=[];if(config.sortForce!=null){var a=config.sortForce;for(var j=0;j<a.length;j++){if(a[j][0]!=i){config.sortList.push(a[j]);}}}config.sortList.push([i,this.order]);}else{if(isValueInArray(i,config.sortList)){for(var j=0;j<config.sortList.length;j++){var s=config.sortList[j],o=config.headerList[s[0]];if(s[0]==i){o.count=s[1];o.count++;s[1]=o.count%2;}}}else{config.sortList.push([i,this.order]);}};setTimeout(function(){setHeadersCss($this[0],$headers,config.sortList,sortCSS);appendToTable($this[0],multisort($this[0],config.sortList,cache));},1);return false;}}).mousedown(function(){if(config.cancelSelection){this._onselectstart_=function(){return false};return false;}});$this.bind("update",function(){var me=this;setTimeout(function(){me.config.parsers=buildParserCache(me,$headers);cache=buildCache(me);},1);}).bind("updateCell",function(e,cell){var config=this.config;var pos=[(cell.parentNode.rowIndex-1),cell.cellIndex];cache.normalized[pos[0]][pos[1]]=config.parsers[pos[1]].format(getElementText(config,cell),cell);}).bind("sorton",function(e,list){$(this).trigger("sortStart");config.sortList=list;var sortList=config.sortList;updateHeaderSortCount(this,sortList);setHeadersCss(this,$headers,sortList,sortCSS);appendToTable(this,multisort(this,sortList,cache));}).bind("appendCache",function(){appendToTable(this,cache);}).bind("applyWidgetId",function(e,id){getWidgetById(id).format(this);}).bind("applyWidgets",function(){applyWidget(this);});if($.metadata&&($(this).metadata()&&$(this).metadata().sortlist)){config.sortList=$(this).metadata().sortlist;}if(config.sortList.length>0){$this.trigger("sorton",[config.sortList]);}applyWidget(this);});};this.addParser=function(parser){var l=parsers.length,a=true;for(var i=0;i<l;i++){if(parsers[i].id.toLowerCase()==parser.id.toLowerCase()){a=false;}}if(a){parsers.push(parser);};};this.addWidget=function(widget){widgets.push(widget);};this.formatFloat=function(s){var i=parseFloat(s);return(isNaN(i))?0:i;};this.formatInt=function(s){var i=parseInt(s);return(isNaN(i))?0:i;};this.isDigit=function(s,config){return/^[-+]?\d*$/.test($.trim(s.replace(/[,.']/g,'')));};this.clearTableBody=function(table){if($.browser.msie){function empty(){while(this.firstChild)this.removeChild(this.firstChild);}empty.apply(table.tBodies[0]);}else{table.tBodies[0].innerHTML="";}};}});$.fn.extend({tablesorter:$.tablesorter.construct});var ts=$.tablesorter;ts.addParser({id:"text",is:function(s){return true;},format:function(s){return $.trim(s.toLocaleLowerCase());},type:"text"});ts.addParser({id:"digit",is:function(s,table){var c=table.config;return $.tablesorter.isDigit(s,c);},format:function(s){return $.tablesorter.formatFloat(s);},type:"numeric"});ts.addParser({id:"currency",is:function(s){return/^[£$€?.]/.test(s);},format:function(s){return $.tablesorter.formatFloat(s.replace(new RegExp(/[£$€]/g),""));},type:"numeric"});ts.addParser({id:"ipAddress",is:function(s){return/^\d{2,3}[\.]\d{2,3}[\.]\d{2,3}[\.]\d{2,3}$/.test(s);},format:function(s){var a=s.split("."),r="",l=a.length;for(var i=0;i<l;i++){var item=a[i];if(item.length==2){r+="0"+item;}else{r+=item;}}return $.tablesorter.formatFloat(r);},type:"numeric"});ts.addParser({id:"url",is:function(s){return/^(https?|ftp|file):\/\/$/.test(s);},format:function(s){return jQuery.trim(s.replace(new RegExp(/(https?|ftp|file):\/\//),''));},type:"text"});ts.addParser({id:"isoDate",is:function(s){return/^\d{4}[\/-]\d{1,2}[\/-]\d{1,2}$/.test(s);},format:function(s){return $.tablesorter.formatFloat((s!="")?new Date(s.replace(new RegExp(/-/g),"/")).getTime():"0");},type:"numeric"});ts.addParser({id:"percent",is:function(s){return/\%$/.test($.trim(s));},format:function(s){return $.tablesorter.formatFloat(s.replace(new RegExp(/%/g),""));},type:"numeric"});ts.addParser({id:"usLongDate",is:function(s){return s.match(new RegExp(/^[A-Za-z]{3,10}\.? [0-9]{1,2}, ([0-9]{4}|'?[0-9]{2}) (([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(AM|PM)))$/));},format:function(s){return $.tablesorter.formatFloat(new Date(s).getTime());},type:"numeric"});ts.addParser({id:"shortDate",is:function(s){return/\d{1,2}[\/\-]\d{1,2}[\/\-]\d{2,4}/.test(s);},format:function(s,table){var c=table.config;s=s.replace(/\-/g,"/");if(c.dateFormat=="us"){s=s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/,"$3/$1/$2");}else if(c.dateFormat=="uk"){s=s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/,"$3/$2/$1");}else if(c.dateFormat=="dd/mm/yy"||c.dateFormat=="dd-mm-yy"){s=s.replace(/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{2})/,"$1/$2/$3");}return $.tablesorter.formatFloat(new Date(s).getTime());},type:"numeric"});ts.addParser({id:"time",is:function(s){return/^(([0-2]?[0-9]:[0-5][0-9])|([0-1]?[0-9]:[0-5][0-9]\s(am|pm)))$/.test(s);},format:function(s){return $.tablesorter.formatFloat(new Date("2000/01/01 "+s).getTime());},type:"numeric"});ts.addParser({id:"metadata",is:function(s){return false;},format:function(s,table,cell){var c=table.config,p=(!c.parserMetadataName)?'sortValue':c.parserMetadataName;return $(cell).metadata()[p];},type:"numeric"});ts.addWidget({id:"zebra",format:function(table){if(table.config.debug){var time=new Date();}var $tr,row=-1,odd;$("tr:visible",table.tBodies[0]).each(function(i){$tr=$(this);if(!$tr.hasClass(table.config.cssChildRow))row++;odd=(row%2==0);$tr.removeClass(table.config.widgetZebra.css[odd?0:1]).addClass(table.config.widgetZebra.css[odd?1:0])});if(table.config.debug){$.tablesorter.benchmark("Applying Zebra widget",time);}}});})(jQuery);
\ No newline at end of file

Modified: trunk/PerformanceTests/resources/results-template.html (128778 => 128779)


--- trunk/PerformanceTests/resources/results-template.html	2012-09-17 18:13:04 UTC (rev 128778)
+++ trunk/PerformanceTests/resources/results-template.html	2012-09-17 18:17:10 UTC (rev 128779)
@@ -6,12 +6,15 @@
 <script src=""
 <script src=""
 <script src=""
+<script src=""
+<script src=""
 <script id="json" type="application/json">%PeformanceTestsResultsJSON%</script>
 <style type="text/css">
 
 section {
-    display: inline-block;
-    padding: 0 10px;
+    background: white;
+    padding: 10px;
+    position: relative;
 }
 
 section h1 {
@@ -27,79 +30,235 @@
     padding: 0px 5px;
 }
 
+body {
+    padding: 0px;
+    margin: 0px;
+    font-family: sans-serif;
+}
+
+table {
+    background: white;
+    width: 100%;
+}
+
+table, td, th {
+    border-collapse: collapse;
+    padding: 5px;
+}
+
+tr.even {
+    background: #f6f6f6;
+}
+
+table td {
+    position: relative;
+    font-family: monospace;
+}
+
+th, td {
+    cursor: pointer;
+    cursor: hand;
+}
+
+th {
+    background: #e6eeee;
+    background: -webkit-gradient(linear, left top, left bottom, from(rgb(244, 244, 244)), to(rgb(217, 217, 217)));
+    border: 1px solid #ccc;
+}
+
+th:after {
+    content: ' \25B8';
+}
+
+th.headerSortUp:after {
+    content: ' \25BE';
+}
+
+th.headerSortDown:after {
+    content: ' \25B4';
+}
+
+td.comparison, td.result {
+    text-align: right;
+}
+
+td.better {
+    color: #6c6;
+}
+
+td.worse {
+    color: #c66;
+}
+
+.checkbox {
+    display: inline-block;
+    background: #eee;
+    background: -webkit-gradient(linear, left bottom, left top, from(rgb(220, 220, 220)), to(rgb(200, 200, 200)));
+    border: inset 1px #ddd;
+    border-radius: 5px;
+    margin: 10px;
+    font-size: small;
+    cursor: pointer;
+    cursor: hand;
+    -webkit-user-select: none;
+    font-weight: bold;
+}
+
+.checkbox span {
+    display: inline-block;
+    line-height: 100%;
+    padding: 5px 8px;
+    border: outset 1px transparent;
+}
+
+.checkbox .checked {
+    background: #e6eeee;
+    background: -webkit-gradient(linear, left top, left bottom, from(rgb(255, 255, 255)), to(rgb(235, 235, 235)));
+    border: outset 1px #eee;
+    border-radius: 5px;
+}
+
 </style>
 </head>
 <body>
-<div id="container"></div>
+<div style="padding: 0 10px;">
+Result <span id="time-memory" class="checkbox"><span class="checked">Time</span><span>Memory</span></span>
+Reference <span id="reference" class="checkbox"></span>
+</div>
 <script>
 
-function createPlot(testName) {
-    var section = $('<section><h1></h1><div class="plot"></div>'
-        + '<span class="tooltip"></span><section>');
-    var unit = testUnits[testName];
-    section.children('.plot').css({'width': 100 * maxLength + 'px', 'height': '300px'});
-    section.children('h1').html(testName + (unit ? ' (' + unit + ')' : ''));
-    $('#container').append(section);
-    
-    attachPlot(testName, section);
+$(document).ready(function () {
+    $('.checkbox').each(function (index, checkbox) {
+        $(checkbox).children('span').click(function (event) {
+            if ($(this).hasClass('checked'))
+                return;
+            $(checkbox).children('span').removeClass('checked');
+            $(this).addClass('checked');
+            $(checkbox).trigger('change', $(this));
+        });
+    });
+})
+
+</script>
+<table id="container"></table>
+<script>
+
+function TestResult(associatedTest, result, associatedRun) {
+    this.unit = function () { return result.unit; }
+    this.test = function () { return associatedTest; }
+    this.unscaledMean = function () { return result.avg; }
+    this.mean = function () { return associatedTest.scalingFactor() * result.avg; }
+    this.min = function () { return associatedTest.scalingFactor() * result.min; }
+    this.max = function () { return associatedTest.scalingFactor() * result.max; }
+    this.stdev = function () { return associatedTest.scalingFactor() * result.stdev; }
+    this.stdevRatio = function () { return result.stdev / result.avg; }
+    this.percentDifference = function(other) { return (other.mean() - this.mean()) / this.mean(); }
+    this.isStatisticallySignificant = function (other) {
+        var diff = Math.abs(other.mean() - this.mean());
+        return diff > this.stdev() && diff > other.stdev();
+    }
+    this.run = function () { return associatedRun; }
 }
 
-function attachPlot(testName, section, minIsZero) {
-    var averages = testResults[testName];
-    var color = 'rgb(230,50,50)';
+function TestRun(entry) {
+    this.description = function () { return entry['description']; }
+    this.webkitRevision = function () { return entry['webkit-revision']; }
+    this.label = function () {
+        var label = 'r' + this.webkitRevision();
+        if (this.description())
+            label += ' &dash; ' + this.description();
+        return label;
+    }
+}
 
-    var minMaxOptions = {lines: {show:true, lineWidth: 0},
-        color: color,
-        points: {show: true, radius: 1},
-        bars: {show: false}};
+function PerfTest(name) {
+    var testResults = [];
+    var cachedUnit = null;
+    var cachedScalingFactor = null;
 
-    function makeLowPlot(id, data) { return $.extend(true, {}, minMaxOptions, {id: id, data: data}); }    
-    function makeHighPlot(from, to, fill, data) { return $.extend(true, {}, minMaxOptions,
-        {id: to, data: data}); }
+    // We can't do this in TestResult because all results for each test need to share the same unit and the same scaling factor.
+    function computeScalingFactorIfNeeded() {
+        // FIXME: We shouldn't be adjusting units on every test result.
+        // We can only do this on the first test.
+        if (!testResults.length || cachedUnit)
+            return;
 
-    var plotData = [
-        makeLowPlot('min', testResultsMin[testName]),
-        makeHighPlot('min', 'max', 0.2, testResultsMax[testName]),
-        makeLowPlot('-&#963;', testResultsStdevLow[testName]), // small letter sgima.
-        makeHighPlot('-&#963;', '+&#963;', 0.4, testResultsStdevHigh[testName]),
-        {data: averages, color: color}];
+        var unit = testResults[0].unit(); // FIXME: We should verify that all results have the same unit.
+        var mean = testResults[0].unscaledMean(); // FIXME: We should look at all values.
+        var kilo = unit == 'bytes' ? 1024 : 1000;
+        if (mean > 10 * kilo * kilo && unit != 'ms') {
+            cachedScalingFactor = 1 / kilo / kilo;
+            cachedUnit = 'M ' + unit;
+        } else if (mean > 10 * kilo) {
+            cachedScalingFactor = 1 / kilo;
+            cachedUnit = unit == 'ms' ? 's' : ('K ' + unit);
+        } else {
+            cachedScalingFactor = 1;
+            cachedUnit = unit;
+        }
+    }
 
+    this.name = function () { return name; }
+    this.isMemoryTest = function () { return name.indexOf(':') >= 0; }
+    this.addResult = function (newResult) {
+        testResults.push(newResult);
+        cachedUnit = null;
+        cachedScalingFactor = null;
+    }
+    this.results = function () { return testResults; }
+    this.scalingFactor = function() {
+        computeScalingFactorIfNeeded();
+        return cachedScalingFactor;
+    }
+    this.unit = function () {
+        computeScalingFactorIfNeeded();
+        return cachedUnit;
+    }
+    this.smallerIsBetter = function () { return this.unit() == 'ms' || this.unit() == 'bytes'; }
+}
+
+var plotColor = 'rgb(230,50,50)';
+var subpointsPlotOptions = {
+    lines: {show:true, lineWidth: 0},
+    color: plotColor,
+    points: {show: true, radius: 1},
+    bars: {show: false}};
+
+var mainPlotOptions = {
+    xaxis: {
+        min: -0.5,
+        tickSize: 1,
+    },
+    crosshair: { mode: 'y' },
+    series: { shadowSize: 0 },
+    bars: {show: true, align: 'center', barWidth: 0.5},
+    lines: { show: false },
+    points: { show: true },
+    grid: {
+        borderWidth: 2,
+        backgroundColor: '#fff',
+        hoverable: true,
+        autoHighlight: false,
+    }
+};
+
+function createPlot(container, test) {
+    var section = $('<section><div class="plot"></div>'
+        + '<span class="tooltip"></span></section>');
+    section.children('.plot').css({'width': 100 * test.results().length + 'px', 'height': '300px'});
+    $(container).append(section);
+
     var plotContainer = section.children('.plot');
-    $.plot(plotContainer, plotData, {
-        xaxis: {
-            min: averages[0][0] - 0.5,
-            max: averages[averages.length - 1][0] + 0.5,
-            tickSize: 1,
-            ticks: averages.map(function (value, index) {
-                var label = 'r' + webkitRevisions[index];
-                if (descriptions[index])
-                    label += ' &dash; ' + descriptions[index]
-                return [index, label];
-            }),
-        },
-        yaxis: {
-            min: minIsZero ? 0 : Math.min.apply(Math, $.map(testResultsMin[testName], function (entry) { return entry[1]; })) * 0.98,
-            max: Math.max.apply(Math, $.map(testResultsMax[testName], function (entry) { return entry[1]; })) * (minIsZero ? 1.1 : 1.01),
-        },
-        crosshair: { mode: 'y' },
-        series: { shadowSize: 0 },
-        bars: {show: true, align: 'center', barWidth: 0.5},
-        lines: { show: false },
-        points: { show: true },
-        grid: {
-            borderWidth: 2,
-            backgroundColor: '#fff',
-            hoverable: true,
-            autoHighlight: false,
-        }
-    });
+    var minIsZero = true;
+    attachPlot(test, plotContainer, minIsZero);
 
     var tooltip = section.children('.tooltip');
     plotContainer.bind('plothover', function (event, position, item) {
         if (item) {
             var postfix = item.series.id ? ' (' + item.series.id + ')' : '';
             tooltip.html(item.datapoint[1].toPrecision(4) + postfix);
-            tooltip.css({left: item.pageX - tooltip.outerWidth() / 2, top: item.pageY + 10});
+            var sectionOffset = $(section).offset();
+            tooltip.css({left: item.pageX - sectionOffset.left - tooltip.outerWidth() / 2, top: item.pageY - sectionOffset.top + 10});
             tooltip.fadeIn(200);
         } else
             tooltip.hide();
@@ -107,52 +266,160 @@
     plotContainer.mouseout(function () {
         tooltip.hide();
     });
-
     plotContainer.click(function (event) {
         event.preventDefault();
-        attachPlot(testName, section, !minIsZero);
+        minIsZero = !minIsZero;
+        attachPlot(test, plotContainer, minIsZero);
     });
+
+    return section;
 }
 
-var results = JSON.parse(document.getElementById('json').textContent);
-var tests = [];
-var testResults = {}, testResultsMin = {}, testResultsMax = {}, testResultsStdevLow = {}, testResultsStdevHigh = {};
-var testUnits = {};
-var webkitRevisions = [];
-var descriptions = [];
-var maxLength = 0;
-$.each(results, function (index, entry) {
-    webkitRevisions.push(entry['webkit-revision']);
-    descriptions.push(entry['description']);
-    $.each(entry.results, function (test, result) {
-        if (tests.indexOf(test) < 0)
-            tests.push(test);
-        if (!testResults[test]) {
-            testResults[test] = [];
-            testResultsMin[test] = [];
-            testResultsMax[test] = [];
-            testResultsStdevLow[test] = [];
-            testResultsStdevHigh[test] = [];
-        }
-        if (typeof result == 'number')
-            testResults[test].push([index, result]);
-        else {
-            testResults[test].push([index, result['avg']]);
-            if ('min' in result)
-                testResultsMin[test].push([index, result['min']]);
-            if ('max' in result)
-                testResultsMax[test].push([index, result['max']]);
-            if ('stdev' in result) {
-                testResultsStdevLow[test].push([index, result['avg'] - result['stdev']]);
-                testResultsStdevHigh[test].push([index, result['avg'] + result['stdev']]);
+function attachPlot(test, plotContainer, minIsZero) {
+    var results = test.results();
+
+    function makeSubpoints(id, callback) { return $.extend(true, {}, subpointsPlotOptions, {id: id, data: results.map(callback)}); }
+    var plotData = [
+        makeSubpoints('min', function (result, index) { return [index, result.min()]; }),
+        makeSubpoints('max', function (result, index) { return [index, result.max()]; }),
+        makeSubpoints('-&#963;', function (result, index) { return [index, result.mean() - result.stdev()]; }),
+        makeSubpoints('+&#963;', function (result, index) { return [index, result.mean() + result.stdev()]; }),
+        {data: results.map(function (result, index) { return [index, result.mean()]; }), color: plotColor}];
+
+    var currentPlotOptions = $.extend(true, {}, mainPlotOptions, {yaxis: {
+        min: minIsZero ? 0 : Math.min.apply(Math, results.map(function (result, index) { return result.min(); })) * 0.98,
+        max: Math.max.apply(Math, results.map(function (result, index) { return result.max(); })) * (minIsZero ? 1.1 : 1.01)}});
+
+    currentPlotOptions.xaxis.max = results.length - 0.5;
+    currentPlotOptions.xaxis.ticks = results.map(function (result, index) { return [index, result.run().label()]; });
+
+    $.plot(plotContainer, plotData, currentPlotOptions);
+}
+
+function toFixedWidthPrecision(value) {
+    var decimal = value.toFixed(2);
+    return decimal;
+}
+
+function formatPercentage(fraction) {
+    var percentage = fraction * 100;
+    return (fraction * 100).toFixed(2) + '%';
+}
+
+function createTable(tests, runs, shouldIgnoreMemory, referenceIndex) {
+    $('#container').html('<thead><tr><th>Test</th><th>Unit</th>' + runs.map(function (run, index) {
+        return '<th colspan="' + (index == referenceIndex ? 2 : 3) + '" class="{sorter: \'comparison\'}">' + run.label() + '</th>';
+    }).reduce(function (markup, cell) { return markup + cell; }, '') + '</tr></head><tbody></tbody>');
+
+    var testNames = [];
+    for (testName in tests)
+        testNames.push(testName);
+
+    testNames.sort().map(function (testName) {
+        var test = tests[testName];
+        if (test.isMemoryTest() != shouldIgnoreMemory)
+            createTableRow(test, test.results()[referenceIndex]);
+    });
+
+    $('#container').tablesorter({widgets: ['zebra']});
+}
+
+function createTableRow(test, referenceResult) {
+    var tableRow = $('<tr><td class="test">' + test.name() + '</td><td class="unit">' + test.unit() + '</td></tr>');
+
+    tableRow.append(test.results().map(function (result, index) {
+        var secondCell = '';
+        var hiddenValue = '';
+        if (result !== referenceResult) {
+            var percentDifference = referenceResult.percentDifference(result);
+            var better = test.smallerIsBetter() ? percentDifference < 0 : percentDifference > 0;
+            var comparison = '';
+            var className = 'comparison';
+            if (referenceResult.isStatisticallySignificant(result)) {
+                comparison = formatPercentage(Math.abs(percentDifference)) + (better ? ' Better' : ' Worse&nbsp;');
+                className += better ? ' better' : ' worse';
             }
+            hiddenValue = '<span style="display: none">|' + comparison + '</span>';
+            secondCell = '</td><td class="' + className + '">' + comparison;
         }
-        maxLength = Math.max(maxLength, testResults[test].length);
-        testUnits[test] = result.unit;
+
+        // Tablesorter doesn't know about the second cell so put the comparison in the invisible element.
+        return '<td class="result">' + toFixedWidthPrecision(result.mean()) + hiddenValue + '</td><td class="stdev">&plusmn; '
+            + formatPercentage(result.stdevRatio()) + secondCell + '</td>';
+    }).reduce(function (markup, cell) { return markup + cell; }, ''));
+
+    $('#container').children('tbody').last().append(tableRow);
+
+    tableRow.click(function (event) {
+        if (event.target != tableRow[0] && event.target.parentNode != tableRow[0])
+            return;
+
+        event.preventDefault();
+
+        var firstCell = tableRow.children('td').first();
+        if (firstCell.children('section').length) {
+            firstCell.children('section').remove();
+            tableRow.children('td').css({'padding-bottom': ''});
+        } else {
+            var plot = createPlot(firstCell, test);
+            plot.css({'position': 'absolute', 'z-index': 2});
+            var offset = tableRow.offset();
+            offset.left += 1;
+            offset.top += tableRow.outerHeight();
+            plot.offset(offset);
+            tableRow.children('td').css({'padding-bottom': plot.outerHeight() + 5});
+        }
+
+        return false;
     });
-});
-$.each(tests.sort(), function (index, test) { createPlot(test); });
+}
 
+function init() {
+    $.tablesorter.addParser({
+        id: 'comparison',
+        is: function(s) {
+            return s.indexOf('|') >= 0;
+        },
+        format: function(s) {
+            var parsed = parseFloat(s.substring(s.indexOf('|') + 1));
+            return isNaN(parsed) ? 0 : parsed;
+        },
+        type: 'numeric',
+    });
+
+    var runs = [];
+    var tests = {};
+    $.each(JSON.parse(document.getElementById('json').textContent), function (index, entry) {
+        var run = new TestRun(entry);
+        runs.push(run);
+        $.each(entry.results, function (test, result) {
+            if (!tests[test])
+                tests[test] = new PerfTest(test);
+            tests[test].addResult(new TestResult(tests[test], result, run));
+        });
+    });
+
+    var shouldIgnoreMemory= true;
+    var referenceIndex = 0;
+    createTable(tests, runs, shouldIgnoreMemory, referenceIndex);
+
+    $('#time-memory').bind('change', function (event, checkedElement) {
+        shouldIgnoreMemory = checkedElement.textContent == 'Time';
+        createTable(tests, runs, shouldIgnoreMemory, referenceIndex);
+    });
+
+    runs.map(function (run, index) {
+        $('#reference').append('<span value="' + index + '"' + (index == referenceIndex ? ' class="checked"' : '') + '>' + run.label() + '</span>');
+    })
+
+    $('#reference').bind('change', function (event, checkedElement) {
+        referenceIndex = parseInt(checkedElement.getAttribute('value'));
+        createTable(tests, runs, shouldIgnoreMemory, referenceIndex);
+    });
+}
+
+init();
+
 </script>
 </body>
 </html>
_______________________________________________
webkit-changes mailing list
[email protected]
http://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to