Home | History | Annotate | Download | only in benchmark
      1 <head>
      2   <title>Page Benchmark Options</title>
      3 
      4   <script src="jst/util.js" type="text/javascript"></script>
      5   <script src="jst/jsevalcontext.js" type="text/javascript"></script>
      6   <script src="jst/jstemplate.js" type="text/javascript"></script>
      7   <script src="jquery/jquery-1.4.2.min.js" type="text/javascript"></script>
      8   <script src="jquery/jquery.flot.min.js" type="text/javascript"></script>
      9   <script src="jquery/jquery.flot.dashes.js" type="text/javascript"></script>
     10   <script src="util/table2CSV.js" type="text/javascript"></script> 
     11   <script src="util/sorttable.js" type="text/javascript"></script>
     12 
     13 <style>
     14 body {
     15   font-size: 84%;
     16   font-family: Helvetica, Arial, sans-serif;
     17   padding: 0.75em;
     18   margin: 0;
     19   min-width: 45em;
     20 }
     21 
     22 h1 {
     23   font-size: 110%;
     24   font-weight: bold;
     25   color: #4a8ee6;
     26   letter-spacing: -1px;
     27   padding: 0;
     28   margin: 0;
     29 }
     30 
     31 div#header {
     32   padding: 0.75em 1em;
     33   padding-top: 0.6em;
     34   padding-left: 10;
     35   margin-bottom: 0.75em;
     36   position: relative;
     37   overflow: hidden;
     38   background: #5296de;
     39   background-size: 100%;
     40   border: 1px solid #3a75bd;
     41   border-radius: 6px;
     42   color: white;
     43   text-shadow: 0 0 2px black;
     44 }
     45 div#header h1 {
     46   padding-left: 37px;
     47   margin: 0;
     48   display: inline;
     49   color: white;
     50 }
     51 div#header p {
     52   font-size: 84%;
     53   font-style: italic;
     54   padding: 0;
     55   margin: 0;
     56   color: white;
     57   padding-left: 0.4em;
     58   display: inline;
     59 }
     60 
     61 table.sortable {
     62   font-size: 84%;
     63   table-layout: fixed;
     64 }
     65 
     66 table.sortable:not([class*='filtered']) tr:nth-child(even) td:not([class*='filtered']) {
     67   background: #eff3ff;
     68 }
     69 
     70 .nobg {
     71   padding: 0 0.5em;
     72   vertical-align: bottom;
     73   font-weight: bold;
     74   color: #315d94;
     75   color: black;
     76   text-align: center;
     77 }
     78 
     79 .bg{
     80   padding: 0 0.5em;
     81   vertical-align: bottom;
     82   font-weight: bold;
     83   color: #315d94;
     84   color: black;
     85   text-align: center;
     86   cursor: pointer;
     87 }
     88 
     89 .bg:hover {
     90   background: #eff3aa;
     91 }
     92 
     93 .avg {
     94   font-weight: bold;
     95   text-align: center;
     96 }
     97 
     98 .data {
     99   text-align: left;
    100   white-space: nowrap;
    101 }
    102 
    103 .bggraph {
    104   white-space: nowrap;
    105 }
    106 
    107 .file_input
    108 {
    109   position: absolute;
    110   width: 140px;
    111   height: 26px;
    112   overflow: hidden;
    113 }
    114 
    115 .file_input_button
    116 {
    117   width: 140px;
    118   position: absolute;
    119   top: 0px;
    120 }
    121 
    122 .file_input_hidden
    123 {
    124   font-size: 25px;
    125   position: absolute;
    126   right: 0px;
    127   top: 0px;
    128   opacity: 0;
    129 }
    130 </style>
    131 
    132 <script>
    133 var max_sample = 0;
    134 
    135 Array.max = function(array) {
    136   return Math.max.apply( Math, array );
    137 }
    138 
    139 Array.min = function(array) {
    140   return Math.min.apply( Math, array );
    141 };
    142 
    143 // Compute the average of an array, removing the min/max.
    144 Array.avg = function(array) {
    145   var count = array.length;
    146   var sum = 0;
    147   var min = array[0];
    148   var max = array[0];
    149   for (var i = 0; i < count; i++) {
    150     sum += array[i];
    151     if (array[i] < min) {
    152       min = array[i];
    153     }
    154     if (array[i] > max) {
    155       max = array[i];
    156     }
    157   }
    158   if (count >= 3) {
    159     sum = sum - min - max;
    160     count -= 2;
    161   }
    162   return sum / count;
    163 }
    164 
    165 // Compute the standard deviation of an array
    166 Array.stddev = function(array) {
    167   var count = array.length;
    168   var mean = Array.avg(array);
    169   var variance = 0;
    170   for (var i = 0; i < count; i++) {
    171     var deviation = mean - array[i];
    172     variance = variance + deviation * deviation;
    173   }
    174   variance = variance / count;
    175   return Math.sqrt(variance);
    176 }
    177 
    178 function handleFileSelect(evt) {
    179   var files = evt.target.files;
    180   for (var i = 0, f; f = files[i]; i++) {
    181     var reader = new FileReader();
    182     reader.onload = function(evt) {
    183       document.getElementById("testurl").value = evt.target.result;
    184     }
    185     reader.readAsText(f);
    186   };
    187 }
    188 
    189 var THTAG = "th";
    190 var TDTAG = "td";
    191 var NONE_DISPLAY = "none";
    192 var CELL_DISPLAY = "table-cell";
    193 var BRIEF_VIEW = "Show More Details";
    194 var FULL_VIEW = "Hide Details";
    195 
    196 // Expand or shrink the result table.
    197 // Called when clicking button "Show More Details/Hide Details".
    198 function expand() {
    199   if (document.getElementById("expand").value == BRIEF_VIEW) {
    200     // From biref view to detailed view.	  
    201     var headers = document.getElementsByTagName(THTAG);
    202     var cells = document.getElementsByTagName(TDTAG);
    203     
    204     // Display the hidden metrics (both headers and data cells).
    205     for (var i = 0; i < headers.length; i++) {
    206       if (headers[i].style.display == NONE_DISPLAY) {
    207         headers[i].style.display = CELL_DISPLAY;
    208       }
    209     }
    210 
    211     for (i = 0; i < cells.length; i++) {
    212       if (cells[i].style.display == NONE_DISPLAY) {
    213         cells[i].style.display = CELL_DISPLAY;
    214       }
    215     }
    216 
    217     document.getElementById("expand").value = FULL_VIEW;
    218   } else {
    219     // From detailed view to brief view.
    220     var headers = document.getElementsByTagName(THTAG);
    221     var cells = document.getElementsByTagName(TDTAG);
    222 
    223     // Hide some metrics.
    224     for (var i = 0; i < headers.length; i++) {
    225       if (headers[i].style.display == CELL_DISPLAY) {
    226         headers[i].style.display = NONE_DISPLAY;
    227       }
    228     }
    229     
    230     for (i = 0; i < cells.length; i++) {
    231       if (cells[i].style.display == CELL_DISPLAY) {
    232         cells[i].style.display = NONE_DISPLAY;
    233       }
    234     }
    235     
    236     document.getElementById("expand").value = BRIEF_VIEW;
    237   }
    238   
    239   // Use cookie to store current expand/hide status. 
    240   var lastValue = document.getElementById("expand").value;
    241   document.cookie = "lastValue=" + lastValue;
    242 }
    243 
    244 // Reloading the page causes table to shrink (default original status).
    245 // Cookie remembers last status of table (in terms of expanded or shrunk).
    246 function restoreTable() {
    247   if (document.cookie == "lastValue=Hide Details") {
    248     var headers = document.getElementsByTagName(THTAG);
    249     var cells = document.getElementsByTagName(TDTAG);
    250     
    251     for (var i = 0; i < headers.length; i++) {
    252       if (headers[i].style.display == NONE_DISPLAY) {
    253         headers[i].style.display = CELL_DISPLAY;
    254       }
    255     }
    256     for (i = 0; i < cells.length; i++) {
    257       if (cells[i].style.display == NONE_DISPLAY) {
    258         cells[i].style.display = CELL_DISPLAY;
    259       }
    260     }
    261     document.getElementById("expand").value = FULL_VIEW;
    262   }
    263 }
    264 
    265 // A class to store the data to plot.
    266 function PData() {
    267   this.xAxis = "Iteration(s)";
    268   this.yAxis = "";
    269   this.A = []; // Two data sets for comparison.
    270   this.B = [];
    271   this.avgA = [];  // Avg value is plotted as a line.
    272   this.avgB = [];
    273   this.maxA = 0;
    274   this.maxB = 0;
    275   this.countA = 0; // Size of the data sets.
    276   this.countB = 0;
    277 
    278   this.setYAxis = function (str) {
    279     this.yAxis = str;
    280   }
    281 
    282   this.setAvg = function (arr, cha) {
    283     if (cha == 'A') {
    284       var avgA = Array.avg(arr);
    285       for (var i = 1; i <= this.countA; i++) {
    286         this.avgA.push([i, avgA]);
    287       }       
    288     } else if (cha == 'B') {
    289       var avgB = Array.avg(arr);
    290       for (var i = 1; i <= this.countB; i++) {
    291         this.avgB.push([i, avgB]);
    292       } 
    293     }
    294   }
    295   
    296   this.setMax = function (arr, cha) {
    297     if (cha == 'A') {
    298       this.maxA = Array.max(arr);
    299     } else if (cha == 'B') {
    300       this.maxB = Array.max(arr);
    301     }
    302   }
    303    
    304   // Add an entry to the array.
    305   this.addArr = function (val, cha) {
    306     if (cha == 'A') {
    307       this.countA++;
    308       this.A.push([this.countA, val]); 
    309     } else if (cha == 'B') {
    310       this.countB++;
    311       this.B.push([this.countB, val]);
    312     }
    313   }
    314   
    315   // Plot the graph at the specified place.
    316   this.plot = function (placeholder) {
    317     $.plot(placeholder,
    318       [// Line A 
    319        {
    320          data: this.A,
    321          label: "A's " + this.yAxis + " in " + this.countA + " " + this.xAxis,
    322          points: {
    323            show: true
    324          },
    325          lines: {
    326            show: true
    327          }
    328        },
    329        
    330        // Line B
    331        { 
    332          data: this.B,
    333          label: "B's " + this.yAxis + " in " + this.countB + " " + this.xAxis,
    334          points: {
    335            show: true
    336          },
    337          lines: {
    338            show: true
    339          }
    340        },
    341        
    342        // Line avgA
    343        { 
    344          data: this.avgA,
    345          label: "A's avg " + this.yAxis,
    346          dashes: {
    347            show: true
    348          }
    349        },
    350        
    351        // Line avgB 
    352        {
    353          data: this.avgB,
    354          label: "B's avg " + this.yAxis,
    355          dashes: {
    356            show: true
    357          }
    358        }],
    359        
    360        // Axis and legend setup.
    361        { xaxis: { 
    362            max: this.countA > this.countB ? this.countA : this.countB,
    363            tickSize: 1,
    364            tickDecimals: 0
    365          },
    366          yaxis: {
    367            // Leave some space for legend.
    368            max: this.maxA > this.maxB ? this.maxA * 1.5 : this.maxB * 1.5 
    369          },
    370          legend: {
    371            backgroundOpacity: 0
    372          }
    373        });
    374   }
    375 }
    376 
    377 // Compare the selected metric of the two selected data sets.
    378 function compare() {
    379   var checkboxArr = document.getElementsByName("checkboxArr");
    380   var radioArr = document.getElementsByName("radioArr");
    381  
    382   if (checkAmount(checkboxArr) != 2) {
    383     alert("please select two rows to compare");
    384     return;
    385   }
    386  
    387   var rowIndexArr = getSelectedIndex(checkboxArr);
    388   var colIndexArr = getSelectedIndex(radioArr);
    389   // To this point, it is for sure that rowIndexArr has two elements 
    390   // while colIndexArr has one.
    391   var selectedRowA = rowIndexArr[0];
    392   var selectedRowB = rowIndexArr[1];
    393   var selectedCol = colIndexArr[0];
    394 
    395   var extension = chrome.extension.getBackgroundPage();
    396   var data = extension.results.data;
    397   var selectedA = getSelectedResults(data,selectedRowA,selectedCol);
    398   var selectedB = getSelectedResults(data,selectedRowB,selectedCol);
    399   var yAxis = getMetricName(selectedCol);
    400  
    401   // Indicate A and B on selected rows.
    402   checkboxArr[selectedRowA].parentElement.firstChild.data = "A";
    403   checkboxArr[selectedRowB].parentElement.firstChild.data = "B";
    404  
    405   plot(selectedA, selectedB, yAxis);
    406 }
    407 
    408 // Show the comparison graph.
    409 function plot(A, B, axis) {
    410   var plotData = new PData();
    411   
    412   plotData.setYAxis(axis);
    413   for (var i = 0; i < A.length; i++) {
    414     plotData.addArr(A[i],'A');
    415   }
    416   for (var i = 0; i < B.length; i++) {
    417     plotData.addArr(B[i],'B');
    418   }
    419   plotData.setAvg(A,'A');
    420   plotData.setAvg(B,'B');
    421   plotData.setMax(A,'A');
    422   plotData.setMax(B,'B');
    423 
    424   var placeholder = document.getElementById("placeholder");
    425   placeholder.style.display = "";  
    426   plotData.plot(placeholder);
    427 }
    428 
    429 var METRIC = {"STARTLOAD": 0, "COMMITLOAD": 1, "DOCLOAD": 2, "PAINT": 3,
    430                "TOTAL": 4, "REQUESTS": 5, "CONNECTS": 6, "READKB": 7,
    431                "WRITEKB": 8, "READKBPS": 9, "WRITEKBPS": 10};
    432 
    433 // Retrieve the metric name from index.
    434 function getMetricName (index) {
    435   switch (index) {
    436     case METRIC.STARTLOAD:
    437       return "Start Load Time";
    438     case METRIC.COMMITLOAD:
    439       return "Commit Load Time";
    440     case METRIC.DOCLOAD:
    441       return "Doc Load Time";
    442     case METRIC.PAINT:
    443       return "Paint Time";
    444     case METRIC.TOTAL:
    445       return "Total Load Time";
    446     case METRIC.REQUESTS: 
    447       return "# Requests";
    448     case METRIC.CONNECTS: 
    449       return "# Connects";
    450     case METRIC.READKB:
    451       return "Read KB";
    452     case METRIC.WRITEKB:
    453       return "Write KB";
    454     case METRIC.READKBPS:
    455       return "Read KBps";
    456     case METRIC.WRITEKBPS:
    457       return "Write KBps";
    458     default:
    459       return ""; 
    460   }
    461 }
    462 
    463 // Get the results with a specific row (data set) and column (metric). 
    464 function getSelectedResults(arr, rowIndex, colIndex) {
    465   switch (colIndex) {
    466     case METRIC.STARTLOAD:
    467       return arr[rowIndex].startLoadResults;
    468     case METRIC.COMMITLOAD:
    469       return arr[rowIndex].commitLoadResults;
    470     case METRIC.DOCLOAD:
    471       return arr[rowIndex].docLoadResults;
    472     case METRIC.PAINT:
    473       return arr[rowIndex].paintResults;
    474     case METRIC.TOTAL:
    475       return arr[rowIndex].totalResults;
    476     case METRIC.REQUESTS:
    477       return arr[rowIndex].requests;
    478     case METRIC.CONNECTS:
    479       return arr[rowIndex].connects;
    480     case METRIC.READKB:
    481       return arr[rowIndex].KbytesRead;
    482     case METRIC.WRITEKB:
    483       return arr[rowIndex].KbytesWritten;
    484     case METRIC.READKBPS:
    485       return arr[rowIndex].readbpsResults;
    486     case METRIC.WRITEKBPS:
    487       return arr[rowIndex].writebpsResults;
    488     default:
    489       return undefined;
    490   }
    491 }
    492 
    493 // Ensure only two data sets (rows) are selected. 
    494 function checkAmount(arr) {
    495   var amount = 0;
    496   for (var i = 0; i < arr.length; i++) { 
    497     if (arr[i].checked) {
    498       amount++;
    499     }
    500   }
    501   return amount;
    502 }
    503 
    504 // Get the index of selected row or column.
    505 function getSelectedIndex(arr) {
    506   var selectedArr = new Array();
    507   for (var i = 0; i < arr.length; i++) {
    508     if(arr[i].checked) {
    509       selectedArr.push(i);
    510     }
    511   }
    512   return selectedArr;
    513 }
    514 
    515 // Repaint or hide the chart.
    516 function updateChart(caller) {
    517   var placeholder = document.getElementById("placeholder");
    518   if (caller.type == "radio") {
    519     // Other radio button is clicked.
    520     if (placeholder.style.display == "") {
    521       compare();
    522     }
    523   } else {
    524     // Other checkbox or clearing results is clicked.
    525     if (placeholder.style.display == "") {
    526       placeholder.style.display = "none";
    527     }
    528   }
    529 }
    530 
    531 // Clear indicators besides checkbox.
    532 function clearIndicator () {
    533   var checkboxArr = document.getElementsByName("checkboxArr");
    534   for (var i = 0; i < checkboxArr.length; i++) {
    535     checkboxArr[i].parentElement.firstChild.data = "";
    536   }
    537 }
    538 
    539 // Enable/Disable buttons according to checkbox change.
    540 function checkSelected () {
    541   var checkboxArr = document.getElementsByName("checkboxArr"); 
    542   if (checkAmount(checkboxArr) !=0) {
    543     document.getElementById("clearSelected").disabled = false;
    544     document.getElementById("compare").disabled = false;
    545   } else {
    546     document.getElementById("clearSelected").disabled = true;
    547     document.getElementById("compare").disabled = true;
    548   }
    549 }
    550 
    551 // Object to summarize everything
    552 var totals = {};
    553 
    554 // Compute the results for a data set.
    555 function computeDisplayResults(data) {
    556   var count = data.data.length;
    557   for (var i = 0; i < count; i++) {
    558     var obj = data.data[i];
    559     obj.displayTime = setDisplayTime(obj.timestamp);
    560     var resultList = obj.totalResults; 
    561     obj.mean = Array.avg(resultList);
    562     obj.stddev = Array.stddev(resultList);
    563     obj.stderr = obj.stddev / Math.sqrt(obj.iterations);
    564     var ci = 1.96 * obj.stderr;
    565     obj.cihigh = obj.mean + ci;
    566     obj.cilow = obj.mean - ci;
    567     obj.min = Array.min(resultList);
    568     obj.max = Array.max(resultList);
    569     obj.readbps = Array.avg(obj.readbpsResults);
    570     obj.writebps = Array.avg(obj.writebpsResults);
    571     obj.readKB = Array.avg(obj.KbytesRead);
    572     obj.writeKB = Array.avg(obj.KbytesWritten);
    573     obj.paintMean = Array.avg(obj.paintResults);
    574     obj.startLoadMean = Array.avg(obj.startLoadResults);
    575     obj.commitLoadMean = Array.avg(obj.commitLoadResults);
    576     obj.docLoadMean = Array.avg(obj.docLoadResults);
    577 
    578     obj.displayRequests = Array.avg(obj.requests);
    579     obj.displayConnects = Array.avg(obj.connects);
    580     obj.displaySpdySessions = Array.avg(obj.spdySessions);
    581     
    582     obj.displayDomNum = obj.domNum;
    583     obj.displayMaxDepth = obj.maxDepth;
    584     obj.displayMinDepth = obj.minDepth;
    585     obj.displayAvgDepth = obj.avgDepth;
    586     }
    587   return count;
    588 }
    589 
    590 // Convert timestamp to readable string.
    591 function setDisplayTime(ts) {
    592   var year = ts.getFullYear();
    593   var mon = ts.getMonth()+1;
    594   var date = ts.getDate();
    595   var hrs = ts.getHours();
    596   var mins = ts.getMinutes();
    597   var secs = ts.getSeconds();
    598   
    599   mon = ( mon < 10 ? "0" : "" ) + mon;
    600   date = ( date < 10 ? "0" : "" ) + date;
    601   mins = ( mins < 10 ? "0" : "" ) + mins;
    602   secs = ( secs < 10 ? "0" : "" ) + secs;
    603   
    604   return (year + "/" + mon + "/" + date + " " + hrs + ":" + mins + ":" + secs);
    605 }
    606 
    607 // Subtract the results from two data sets.
    608 // This function could be smarter about what it subtracts,
    609 // for now it just subtracts everything.
    610 // Returns true if it was able to compare the two data sets.
    611 function subtractData(data, baseline) {
    612   var count = data.data.length;
    613   if (baseline.data.length != count) {
    614     return false;
    615   }
    616   for (var i = 0; i < count; i++) {
    617     var obj = data.data[i];
    618     var obj2 = baseline.data[i];
    619 
    620     // The data sets are different.
    621     if (obj.url != obj2.url ||
    622         obj.iterations != obj2.iterations) {
    623       return false;
    624     }
    625 
    626     obj.mean -= obj2.mean;
    627     obj.stddev -= obj2.stddev;
    628     obj.min -= obj2.min;
    629     obj.max -= obj2.max;
    630     obj.readbps -= obj2.readbps;
    631     obj.writebps -= obj2.writebps;
    632     obj.readKB -= obj2.readKB;
    633     obj.writeKB -= obj2.writeKB;
    634     obj.paintMean -= obj2.paintMean;
    635     obj.startLoadMean -= obj2.startLoadMean;
    636     obj.commitLoadMean -= obj2.commitLoadMean;
    637     obj.docLoadMean -= obj2.docLoadMean;
    638 
    639     obj.displayRequests -= obj2.displayRequests;
    640     obj.displayConnects -= obj2.displayConnects;
    641     obj.displaySpdySessions -= obj2.displaySpdySessions;
    642   }
    643   return true;
    644 }
    645 
    646 // Compute totals based on a data set.
    647 function computeTotals(data) {
    648   var count = data.data.length;
    649   for (var i = 0; i < count; i++) {
    650     var obj = data.data[i];
    651     totals.mean += obj.mean;
    652     totals.paintMean += obj.paintMean;
    653     totals.startLoadMean += obj.startLoadMean;
    654     totals.commitLoadMean += obj.commitLoadMean;
    655     totals.docLoadMean += obj.docLoadMean;
    656   }
    657 }
    658 
    659 // Compute results for the data with an optional baseline.
    660 // If |baseline| is undefined, will compute the results of this
    661 // run.  Otherwise, computes the diff between this data and the baseline.
    662 function computeResults(data, baseline) {
    663   totals = {};
    664   totals.mean = 0;
    665   totals.paintMean = 0;
    666   totals.startLoadMean = 0;
    667   totals.commitLoadMean = 0;
    668   totals.docLoadMean = 0;
    669 
    670   var count = computeDisplayResults(data);
    671 
    672   if (baseline) {
    673     computeDisplayResults(baseline);
    674     if (!subtractData(data, baseline)) {
    675       alert("These data sets are different");
    676       document.getElementById("baseline").value = "";
    677       return;
    678     }
    679   }
    680 
    681   computeTotals(data);
    682   totals.url = "(" + count + " urls)";
    683   if (count > 0) {
    684     totals.mean /= count;
    685     totals.paintMean /= count;
    686     totals.startLoadMean /= count;
    687     totals.commitLoadMean /= count;
    688     totals.docLoadMean /= count;
    689   }
    690 
    691   // Find the biggest average for our bar graph.
    692   max_sample = 0;
    693   for (var i = 0; i < data.data.length; i++) {
    694     if (data.data[i].max > max_sample) {
    695       max_sample = data.data[i].mean;
    696     }
    697   }
    698 }
    699 
    700 function jsinit() {
    701   var extension = chrome.extension.getBackgroundPage();
    702 
    703   // Run the template to show results
    704   var data = extension.results;
    705 
    706   // Get the baseline results
    707   var elt = document.getElementById("baseline");
    708   var baseline_json = document.getElementById("baseline").value;
    709   var baseline;
    710   if (baseline_json) {
    711     try {
    712       baseline = JSON.parse(baseline_json);
    713     } catch (e) {
    714       alert("JSON parse error: " + e);
    715     }
    716   }
    717 
    718   // Compute
    719   computeResults(data, baseline);
    720 
    721   var context = new JsEvalContext(data);
    722   context.setVariable('$width', 0);
    723   context.setVariable('$samples', 0);
    724   var template = document.getElementById("t");
    725   jstProcess(context, template);
    726 
    727   // Set the options
    728   document.getElementById("iterations").value = extension.iterations;
    729   document.getElementById("clearconns").checked = extension.clearConnections;
    730   document.getElementById("clearcache").checked = extension.clearCache;
    731   document.getElementById("enablespdy").checked = extension.enableSpdy;
    732   setUrl(extension.testUrl);
    733 
    734   if (!baseline) {
    735     var json_data = JSON.stringify(data);
    736     document.getElementById("json").value = json_data;
    737   }
    738 
    739   // Activate loading Urls from local file.
    740   document.getElementById('files').addEventListener('change', 
    741                                                     handleFileSelect, false);
    742 }
    743 
    744 function getWidth(mean, obj) {
    745   var kMinWidth = 200;
    746   var max_width = obj.offsetWidth;
    747   if (max_width < kMinWidth) {
    748     max_width = kMinWidth;
    749   }
    750   return Math.floor(max_width * (mean / max_sample));
    751 }
    752 
    753 // Apply configuration back to our extension
    754 function config() {
    755   var extension = chrome.extension.getBackgroundPage();
    756   var iterations = parseInt(document.getElementById("iterations").value);
    757   var clearConnections = document.getElementById("clearconns").checked;
    758   var clearCache = document.getElementById("clearcache").checked;
    759   var enableSpdy = document.getElementById("enablespdy").checked;
    760   if (iterations > 0) {
    761     extension.iterations = iterations;
    762     extension.clearConnections = clearConnections;
    763     extension.clearCache = clearCache;
    764     extension.enableSpdy = enableSpdy;
    765   }
    766 }
    767 
    768 // Set the url in the benchmark url box.
    769 function setUrl(url) {
    770   document.getElementById("testurl").value = url;
    771 }
    772 
    773 // Start the benchmark.
    774 function run() {
    775   if (!chrome.benchmarking) {
    776     alert("Warning:  Looks like you forgot to run chrome with " +
    777           " --enable-benchmarking set.");
    778     return;
    779   }
    780   var extension = chrome.extension.getBackgroundPage();
    781   var testUrl = document.getElementById("testurl").value;
    782   extension.testUrl = testUrl;
    783   extension.run();
    784 }
    785 
    786 function showConfirm() {
    787   var r = confirm("Are you sure to clear results?");
    788   if (r) {
    789     // Find out the event source element.
    790     var evtSrc = window.event.srcElement; 
    791     if (evtSrc.value == "Clear Selected") {
    792       clearSelected();
    793     } else if (evtSrc.value == "Clear All") {
    794       clearResults();
    795     }
    796   }
    797 }
    798 
    799 // Clear the selected results
    800 function clearSelected() {
    801   var extension = chrome.extension.getBackgroundPage();
    802   var checkboxArr = document.getElementsByName("checkboxArr");
    803   var rowIndexArr = getSelectedIndex(checkboxArr);
    804   var currIndex;
    805   for (var i = 0; i < rowIndexArr.length; i++) {
    806     currIndex = rowIndexArr[i];
    807     // Update the index of the original row in the modified array. 
    808     currIndex -= i;
    809     extension.results.data.splice(currIndex, 1);
    810     document.location.reload(true);
    811     updateChart(this);
    812     jsinit();
    813   }
    814 }
    815 
    816 // Clear all the results
    817 function clearResults() {
    818   var extension = chrome.extension.getBackgroundPage();
    819   extension.results = {};
    820   extension.results.data = new Array();
    821   document.getElementById("json").value = "";
    822   document.getElementById("baseline").value = "";
    823   updateChart(this);
    824   jsinit();
    825 }
    826 
    827 // Export html table into CSV format.
    828 function export() {
    829   var checkboxArr = document.getElementsByName("checkboxArr");
    830   var rowNum = checkboxArr.length + 1; // # of data rows plus total-stats row. 
    831   $('#t').table2CSV(rowNum);
    832 }
    833 
    834 // Toggle display of an element
    835 function toggle(id) {
    836   var elt = document.getElementById(id);
    837   if (elt.style.display == "none") {
    838     elt.style.display = "block";
    839   } else {
    840     elt.style.display = "none";
    841   }
    842 }
    843 </script>
    844 
    845 </head>
    846 
    847 <body onload="jsinit(); restoreTable()">
    848 
    849 <h1><div id="header">Page Benchmark Results</div></h1>
    850 
    851 <h1>Configuration <a href="http://sites.google.com/a/chromium.org/dev/chrome-benchmarking-extension" target="_blank" style="float:right"><font size="4%">Help</font></a></h1>
    852 
    853 <span>Iterations</span> 
    854 <input id="iterations" type="text" style="text-align:right" size="4">
    855 Clear Connections?<input id="clearconns" type="checkbox">
    856 Clear Cache?<input id="clearcache" type="checkbox">
    857 Enable Spdy?<input id="enablespdy" type="checkbox">
    858 <br>
    859 <span>URLs to load</span> <input type="text" id="testurl" size="80">
    860 <span class="file_input">
    861 <input class="file_input_button" type="button" value="Load URLs From File" />
    862 <input class="file_input_hidden" type="file" id="files" name="files[]" multiple />
    863 </span>
    864 <form onsubmit="config();run()">
    865 <input type="submit" value="Run">
    866 </form>
    867 <p>
    868 
    869 <h1>Results</h1>
    870 
    871 <input id="expand" type="button" value="Show More Details" onclick="expand()">
    872 <input id="clearSelected" type="button" value="Clear Selected" disabled="true" onclick="showConfirm()">
    873 <input id="clearAll" type="button" value="Clear All" onclick="showConfirm()">
    874 <input type="button" value="Export As .csv" onclick="export()">
    875 <table id="t" class="sortable" width="100%">
    876   <tr>
    877   <th width=35 class="nobg"></th>
    878   <th width=215 class="nobg">url</th>
    879   <th width=110 class="nobg" style="display:none">timestamp</th>
    880   <th width=50 class="nobg">iterations</th>
    881   <th width=50 class="nobg">via spdy</th>
    882   <th width=50 class="bg" style="display:none">start load mean</th>
    883   <th width=50 class="bg" style="display:none">commit load mean</th>
    884   <th width=50 class="bg">doc load mean</th>
    885   <th width=50 class="bg">paint mean</th>
    886   <th width=50 class="bg">total load mean</th>
    887   <th width=50 class="bg">stddev</th>
    888   <th width=50 class="bg" style="display:none">stderr</th>
    889   <th width=50 class="bg" style="display:none">95% CI-low</th>
    890   <th width=50 class="bg" style="display:none">95% CI-high</th>
    891   <th width=50 class="bg" style="display:none">min</th>
    892   <th width=50 class="bg" style="display:none">max</th>
    893   <th width=60 class="bg" style="display:none"># Requests</th>
    894   <th width=60 class="bg" style="display:none"># Connects</th>
    895   <th width=50 class="bg" style="display:none"># SPDY Sessions</th>
    896   <th width=50 class="bg" style="display:none">Read KB</th>
    897   <th width=50 class="bg" style="display:none">Write KB</th>
    898   <th width=50 class="bg">Read KBps</th>
    899   <th width=50 class="bg">Write KBps</th>
    900   <th width=50 class="bg"># DOM</th>
    901   <th width=70 class="bg" style="display:none">max DOM depth</th>
    902   <th width=30 class="bg" style="display:none">min</th>
    903   <th width=30 class="bg" style="display:none">avg</th>
    904   <th samples class="nobg" style="display:none">total loan time samples</th>
    905   </tr>
    906 
    907   <tr id="t.total" jsselect="totals">
    908   <td class="avg" jseval="1"></td>
    909   <td class="url">TOTALS <span jscontent="url"></span></td>
    910   <td class="avg" style="display:none"></td>
    911   <td class="avg" jseval="1"></td>
    912   <td class="avg" jseval="1"></td>
    913   <td class="avg" style="display:none"><span jseval="val = startLoadMean.toFixed(1)" jscontent="val"></span></td>
    914   <td class="avg" style="display:none"><span jseval="val = commitLoadMean.toFixed(1)" jscontent="val"></span></td>
    915   <td class="avg"><span jseval="val = docLoadMean.toFixed(1)" jscontent="val"></span></td>
    916   <td class="avg"><span jseval="val = paintMean.toFixed(1)" jscontent="val"></span></td>
    917   <td class="avg"><span jseval="val = mean.toFixed(1)" jscontent="val"></span></td>
    918   <td class="avg" jseval="1"></td>
    919   <td class="avg" jseval="1"></td>
    920   <td class="avg" jseval="1"></td>
    921   <td class="avg" jseval="1"></td>
    922   <td class="avg" jseval="1"></td>
    923   <td class="avg" jseval="1"></td>
    924   <td class="avg" jseval="1"></td>
    925   <td class="avg" jseval="1"></td>
    926   <td class="avg" jseval="1"></td>
    927   <td class="avg" jseval="1"></td>
    928   <td class="avg" jseval="1"></td>
    929   <td class="avg" jseval="1"></td>
    930   <td class="avg" jseval="1"></td>
    931   <td class="avg" jseval="1"></td>
    932   <td class="avg" jseval="1"></td>
    933   <td class="avg" jseval="1"></td>
    934   <td class="avg" jseval="1"></td>
    935   <td class="data"></td>
    936   </tr>
    937 
    938   <tr jsselect="data">
    939   <td align=right> <input type="checkbox" name="checkboxArr" onclick="updateChart(this);clearIndicator();checkSelected()"></td>
    940   <td class="url" jseval="$width = getWidth($this.mean, this); url.length > 40 ? $suburl = url.substring(0,27) + '...' + url.substring(url.length-10, url.length) : $suburl=url"><div jsvalues=".style.width:$width" class="bggraph"><a jsvalues="href:$this.url" jscontent="$suburl"></a></div></td>
    941   <td class="avg" style="display:none" jseval="val = displayTime" jscontent="val"></td>
    942   <td class="avg" jseval="val = iterations" jscontent="val"></td>
    943   <td class="avg" jseval="val = viaSpdy" jscontent="val"></td>
    944   <td class="avg" style="display:none" jseval="val = startLoadMean.toFixed(1)" jscontent="val"></td>
    945   <td class="avg" style="display:none" jseval="val = commitLoadMean.toFixed(1)" jscontent="val"></td>
    946   <td class="avg" jseval="val = docLoadMean.toFixed(1)" jscontent="val"></td>
    947   <td class="avg" jseval="val = paintMean.toFixed(1)" jscontent="val"></td>
    948   <td class="avg" jseval="val = mean.toFixed(1)" jscontent="val"></td>
    949   <td class="avg" jseval="val = stddev.toFixed(1)" jscontent="val"></td>
    950   <td class="avg" style="display:none" jseval="val = stderr.toFixed(1)" jscontent="val"></td>
    951   <td class="avg" style="display:none" jseval="val = cilow.toFixed(1)" jscontent="val"></td>
    952   <td class="avg" style="display:none" jseval="val = cihigh.toFixed(1)" jscontent="val"></td>
    953   <td class="avg" style="display:none" jseval="val = min.toFixed(1)" jscontent="val"></td>
    954   <td class="avg" style="display:none" jseval="val = max.toFixed(1)" jscontent="val"></td>
    955   <td class="avg" style="display:none" jseval="val = displayRequests.toFixed(1)" jscontent="val"></td>
    956   <td class="avg" style="display:none" jseval="val = displayConnects.toFixed(1)" jscontent="val"></td>
    957   <td class="avg" style="display:none" jseval="val = displaySpdySessions.toFixed(1)" jscontent="val"></td>
    958   <td class="avg" style="display:none" jseval="val = readKB.toFixed(1)" jscontent="val"></td>
    959   <td class="avg" style="display:none" jseval="val = writeKB.toFixed(1)" jscontent="val"></td>
    960   <td class="avg" jseval="val = readbps.toFixed(1)" jscontent="val"></td>
    961   <td class="avg" jseval="val = writebps.toFixed(1)" jscontent="val"></td>
    962   <td class="avg" jseval="val = displayDomNum" jscontent="val"></td>
    963   <td class="avg" style="display:none" jseval="val = displayMaxDepth" jscontent="val"></td>
    964   <td class="avg" style="display:none" jseval="val = displayMinDepth" jscontent="val"></td>
    965   <td class="avg" style="display:none" jseval="val = displayAvgDepth.toFixed(1)" jscontent="val"></td>
    966   <td class="data" style="display:none"><span jsselect="totalResults"><span jscontent="$this"></span>,</span> </td>
    967   </tr>
    968   <tr jsdisplay="data.length == 0">
    969   <td colspan=2>No tests have been run yet.</td>
    970   </tr>
    971   <tr jsdisplay="data.length > 1">
    972   <td width=25 jseval="1"></td>
    973   <td class="url" jseval="1"></td>
    974   <td class="avg" style="display:none" jseval="1"></td>
    975   <td class="avg" jseval="1"></td>
    976   <td class="avg" jseval="1"></td>
    977   <td class="avg" style="display:none"><input name="radioArr" type="radio" onclick="updateChart(this)"> </td>
    978   <td class="avg" style="display:none"><input name="radioArr" type="radio" onclick="updateChart(this)"></td>
    979   <td class="avg"><input name="radioArr" type="radio" onclick="updateChart(this)"></td>
    980   <td class="avg"><input name="radioArr" type="radio" onclick="updateChart(this)"></td>
    981   <td class="avg"><input name="radioArr" type="radio" onclick="updateChart(this)" checked></td>
    982   <td class="avg" jseval="1"></td>
    983   <td class="avg" style="display:none" jseval="1"></td>
    984   <td class="avg" style="display:none" jseval="1"></td>
    985   <td class="avg" style="display:none" jseval="1"></td>
    986   <td class="avg" style="display:none" jseval="1"></td>
    987   <td class="avg" style="display:none" jseval="1"></td>
    988   <td class="avg" style="display:none"><input name="radioArr" type="radio" onclick="updateChart(this)"></td>
    989   <td class="avg" style="display:none"><input name="radioArr" type="radio" onclick="updateChart(this)"></td>
    990   <td class="avg" style="display:none" jseval="1"></td>
    991   <td class="avg" style="display:none"><input name="radioArr" type="radio" onclick="updateChart(this)"></td>
    992   <td class="avg" style="display:none"><input name="radioArr" type="radio" onclick="updateChart(this)"></td>
    993   <td class="avg"><input name="radioArr" type="radio" onclick="updateChart(this)"></td>
    994   <td class="avg"><input name="radioArr" type="radio" onclick="updateChart(this)"></td>
    995   </tr>
    996   <tr jsdisplay="data.length > 1">
    997   <td> <input id="compare" type="button" value="Compare" disabled="true" onclick="compare()"></td>
    998   </tr>
    999 </table>
   1000 <hr>
   1001 <center>
   1002 <div id="placeholder" style="width:430px;height:230px;display:none">graph place</div>
   1003 </center>
   1004 <span onclick="toggle('json')">JSON data</span><br>
   1005 <textarea style="display:none" type=text id=json rows=10 cols=50></textarea><p>
   1006 
   1007 <span onclick="toggle('baseline')">COMPARE to</span><br>
   1008 <textarea style="display:none" type=text id=baseline rows=10 cols=50
   1009 onchange="jsinit()"></textarea><p>
   1010 </body>
   1011