Home | History | Annotate | Download | only in src
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 'use strict';
      6 
      7 /**
      8  * @fileoverview Analysis summarizes info about the selected slices
      9  * to the analysis panel.
     10  */
     11 base.require('analysis.util');
     12 base.require('ui');
     13 base.requireStylesheet('timeline_analysis_view');
     14 base.exportTo('tracing', function() {
     15 
     16   var AnalysisResults = tracing.ui.define('div');
     17 
     18   AnalysisResults.prototype = {
     19     __proto__: HTMLDivElement.prototype,
     20 
     21     decorate: function() {
     22     },
     23 
     24     appendElement_: function(parent, tagName, opt_text) {
     25       var n = parent.ownerDocument.createElement(tagName);
     26       parent.appendChild(n);
     27       if (opt_text != undefined)
     28         n.textContent = opt_text;
     29       return n;
     30     },
     31 
     32     appendText_: function(parent, text) {
     33       var textElement = parent.ownerDocument.createTextNode(text);
     34       parent.appendChild(textNode);
     35       return textNode;
     36     },
     37 
     38     appendTableCell_: function(table, row, cellnum, text) {
     39       var td = this.appendElement_(row, 'td', text);
     40       td.className = table.className + '-col-' + cellnum;
     41       return td;
     42     },
     43 
     44     appendTableCellWithTooltip_: function(table, row, cellnum, text, tooltip) {
     45       if (tooltip) {
     46         var td = this.appendElement_(row, 'td');
     47         td.className = table.className + '-col-' + cellnum;
     48         var span = this.appendElement_(td, 'span', text);
     49         span.className = 'tooltip';
     50         span.title = tooltip;
     51         return td;
     52       } else {
     53         this.appendTableCell_(table, row, cellnum, text);
     54       }
     55     },
     56 
     57     /**
     58      * Adds a table with the given className.
     59      * @return {HTMLTableElement} The newly created table.
     60      */
     61     appendTable: function(className, numColumns) {
     62       var table = this.appendElement_(this, 'table');
     63       table.className = className + ' analysis-table';
     64       table.numColumns = numColumns;
     65       return table;
     66     },
     67 
     68     /**
     69      * Creates and appends a row to |table| with a left-aligned |label]
     70      * header that spans all columns.
     71      */
     72     appendTableHeader: function(table, label) {
     73       var row = this.appendElement_(table, 'tr');
     74 
     75       var th = this.appendElement_(row, 'th', label);
     76       th.className = 'analysis-table-header';
     77     },
     78 
     79     /**
     80      * Creates and appends a row to |table| with a left-aligned |label]
     81      * in the first column and an optional |opt_text| value in the second
     82      * column.
     83      */
     84     appendSummaryRow: function(table, label, opt_text) {
     85       var row = this.appendElement_(table, 'tr');
     86       row.className = 'analysis-table-row';
     87 
     88       this.appendTableCell_(table, row, 0, label);
     89       if (opt_text !== undefined) {
     90         if (opt_text[0] == '{' && opt_text[opt_text.length - 1] == '}') {
     91           // Try to treat the opt_text as json.
     92           var value;
     93           try {
     94             value = JSON.parse(opt_text)
     95           } catch(e) {
     96             value = undefined;
     97           }
     98           if (!value === undefined) {
     99             this.appendTableCell_(table, row, 1, opt_text);
    100           } else {
    101             var pretty = JSON.stringify(value, null, ' ');
    102             this.appendTableCell_(table, row, 1, pretty);
    103           }
    104         } else {
    105           this.appendTableCell_(table, row, 1, opt_text);
    106         }
    107         for (var i = 2; i < table.numColumns; i++)
    108           this.appendTableCell_(table, row, i, '');
    109       } else {
    110         for (var i = 1; i < table.numColumns; i++)
    111           this.appendTableCell_(table, row, 1, '');
    112       }
    113     },
    114 
    115     /**
    116      * Adds a spacing row to spread out results.
    117      */
    118     appendSpacingRow: function(table) {
    119       var row = this.appendElement_(table, 'tr');
    120       row.className = 'analysis-table-row';
    121       for (var i = 0; i < table.numColumns; i++)
    122         this.appendTableCell_(table, row, i, ' ');
    123     },
    124 
    125     /**
    126      * Creates and appends a row to |table| with a left-aligned |label]
    127      * in the first column and a millisecvond |time| value in the second
    128      * column.
    129      */
    130     appendSummaryRowTime: function(table, label, time) {
    131       this.appendSummaryRow(table, label,
    132                             tracing.analysis.tsRound(time) + ' ms');
    133     },
    134 
    135     /**
    136      * Creates and appends a row to |table| that summarizes one or more slices,
    137      * or one or more counters.
    138      * The row has a left-aligned |label| in the first column, the |duration|
    139      * of the data in the second, the number of |occurrences| in the third.
    140      * @param {object} opt_statistics May be undefined, or an object which
    141      * contains calculated staistics containing min/max/avg for slices, or
    142      * min/max/avg/start/end for counters.
    143      */
    144     appendDataRow: function(
    145         table, label, opt_duration, opt_occurences, opt_statistics) {
    146 
    147       var tooltip = undefined;
    148       if (opt_statistics) {
    149         tooltip = 'Min Duration:\u0009' +
    150                   tracing.analysis.tsRound(opt_statistics.min) +
    151                   ' ms \u000DMax Duration:\u0009' +
    152                   tracing.analysis.tsRound(opt_statistics.max) +
    153                   ' ms \u000DAvg Duration:\u0009' +
    154                   tracing.analysis.tsRound(opt_statistics.avg) +
    155                   ' ms (\u03C3 = ' +
    156                   tracing.analysis.tsRound(opt_statistics.avg_stddev) + ')';
    157 
    158         if (opt_statistics.start) {
    159           tooltip += '\u000DStart Time:\u0009' +
    160               tracing.analysis.tsRound(opt_statistics.start) + ' ms';
    161         }
    162         if (opt_statistics.end) {
    163           tooltip += '\u000DEnd Time:\u0009' +
    164               tracing.analysis.tsRound(opt_statistics.end) + ' ms';
    165         }
    166         if (opt_statistics.frequency && opt_statistics.frequency_stddev) {
    167           tooltip += '\u000DFrequency:\u0009' +
    168               tracing.analysis.tsRound(opt_statistics.frequency) +
    169               ' occurrences/s (\u03C3 = ' +
    170               tracing.analysis.tsRound(opt_statistics.frequency_stddev) + ')';
    171         }
    172       }
    173 
    174       var row = this.appendElement_(table, 'tr');
    175       row.className = 'analysis-table-row';
    176 
    177       this.appendTableCellWithTooltip_(table, row, 0, label, tooltip);
    178 
    179       if (opt_duration !== undefined) {
    180         this.appendTableCellWithTooltip_(table, row, 1,
    181             tracing.analysis.tsRound(opt_duration) + ' ms', tooltip);
    182       } else {
    183         this.appendTableCell_(table, row, 1, '');
    184       }
    185 
    186       if (opt_occurences !== undefined) {
    187         this.appendTableCellWithTooltip_(table, row, 2,
    188             String(opt_occurences) + ' occurrences', tooltip);
    189 
    190       } else {
    191         this.appendTableCell_(table, row, 2, '');
    192       }
    193     }
    194   };
    195 
    196   /**
    197    * Analyzes the selection, outputting the analysis results into the provided
    198    * results object.
    199    *
    200    * @param {AnalysisResults} results Where the analysis is placed.
    201    * @param {Selection} selection What to analyze.
    202    */
    203   function analyzeSelection(results, selection) {
    204 
    205     var sliceHits = selection.getSliceHitsAsSelection();
    206     var counterSampleHits = selection.getCounterSampleHitsAsSelection();
    207 
    208     if (sliceHits.length == 1) {
    209       var slice = sliceHits[0].slice;
    210       var table = results.appendTable('analysis-slice-table', 2);
    211 
    212       results.appendTableHeader(table, 'Selected slice:');
    213       results.appendSummaryRow(table, 'Title', slice.title);
    214 
    215       if (slice.category)
    216         results.appendSummaryRow(table, 'Category', slice.category);
    217 
    218       results.appendSummaryRowTime(table, 'Start', slice.start);
    219       results.appendSummaryRowTime(table, 'Duration', slice.duration);
    220 
    221       if (slice.durationInUserTime) {
    222         results.appendSummaryRowTime(
    223             table, 'Duration (U)', slice.durationInUserTime);
    224       }
    225 
    226       var n = 0;
    227       for (var argName in slice.args) {
    228         n += 1;
    229       }
    230       if (n > 0) {
    231         results.appendSummaryRow(table, 'Args');
    232         for (var argName in slice.args) {
    233           var argVal = slice.args[argName];
    234           // TODO(sleffler) use span instead?
    235           results.appendSummaryRow(table, ' ' + argName, argVal);
    236         }
    237       }
    238     } else if (sliceHits.length > 1) {
    239       var tsLo = sliceHits.bounds.min;
    240       var tsHi = sliceHits.bounds.max;
    241 
    242       // compute total sliceHits duration
    243       var titles = sliceHits.map(function(i) { return i.slice.title; });
    244 
    245       var numTitles = 0;
    246       var slicesByTitle = {};
    247       for (var i = 0; i < sliceHits.length; i++) {
    248         var slice = sliceHits[i].slice;
    249         if (!slicesByTitle[slice.title]) {
    250           slicesByTitle[slice.title] = {
    251             slices: []
    252           };
    253           numTitles++;
    254         }
    255         slicesByTitle[slice.title].slices.push(slice);
    256       }
    257 
    258       var table;
    259       table = results.appendTable('analysis-slices-table', 3);
    260       results.appendTableHeader(table, 'Slices:');
    261 
    262       var totalDuration = 0;
    263       for (var sliceGroupTitle in slicesByTitle) {
    264         var sliceGroup = slicesByTitle[sliceGroupTitle];
    265         var duration = 0;
    266         var avg = 0;
    267         var startOfFirstOccurrence = Number.MAX_VALUE;
    268         var startOfLastOccurrence = -Number.MAX_VALUE;
    269         var frequencyDetails = undefined;
    270         var min = Number.MAX_VALUE;
    271         var max = -Number.MAX_VALUE;
    272         for (var i = 0; i < sliceGroup.slices.length; i++) {
    273           duration += sliceGroup.slices[i].duration;
    274           startOfFirstOccurrence = Math.min(sliceGroup.slices[i].start,
    275                                             startOfFirstOccurrence);
    276           startOfLastOccurrence = Math.max(sliceGroup.slices[i].start,
    277               startOfLastOccurrence);
    278           min = Math.min(sliceGroup.slices[i].duration, min);
    279           max = Math.max(sliceGroup.slices[i].duration, max);
    280         }
    281 
    282         totalDuration += duration;
    283 
    284         if (sliceGroup.slices.length == 0)
    285           avg = 0;
    286         avg = duration / sliceGroup.slices.length;
    287 
    288         var details = {min: min,
    289           max: max,
    290           avg: avg,
    291           avg_stddev: undefined,
    292           frequency: undefined,
    293           frequency_stddev: undefined};
    294 
    295         // Compute the stddev of the slice durations.
    296         var sumOfSquaredDistancesToMean = 0;
    297         for (var i = 0; i < sliceGroup.slices.length; i++) {
    298           var signedDistance = details.avg - sliceGroup.slices[i].duration;
    299           sumOfSquaredDistancesToMean += signedDistance * signedDistance;
    300         }
    301 
    302         details.avg_stddev = Math.sqrt(
    303             sumOfSquaredDistancesToMean / (sliceGroup.slices.length - 1));
    304 
    305         // We require at least 3 samples to compute the stddev.
    306         var elapsed = startOfLastOccurrence - startOfFirstOccurrence;
    307         if (sliceGroup.slices.length > 2 && elapsed > 0) {
    308           var numDistances = sliceGroup.slices.length - 1;
    309           details.frequency = (1000 * numDistances) / elapsed;
    310 
    311           // Compute the stddev.
    312           sumOfSquaredDistancesToMean = 0;
    313           for (var i = 1; i < sliceGroup.slices.length; i++) {
    314             var currentFrequency = 1000 /
    315                 (sliceGroup.slices[i].start - sliceGroup.slices[i - 1].start);
    316             var signedDistance = details.frequency - currentFrequency;
    317             sumOfSquaredDistancesToMean += signedDistance * signedDistance;
    318           }
    319 
    320           details.frequency_stddev = Math.sqrt(
    321               sumOfSquaredDistancesToMean / (numDistances - 1));
    322         }
    323         results.appendDataRow(
    324             table, sliceGroupTitle, duration, sliceGroup.slices.length,
    325             details);
    326       }
    327       results.appendDataRow(table, '*Totals', totalDuration, sliceHits.length);
    328       results.appendSpacingRow(table);
    329       results.appendSummaryRowTime(table, 'Selection start', tsLo);
    330       results.appendSummaryRowTime(table, 'Selection extent', tsHi - tsLo);
    331     }
    332 
    333     if (counterSampleHits.length == 1) {
    334       var hit = counterSampleHits[0];
    335       var ctr = hit.counter;
    336       var sampleIndex = hit.sampleIndex;
    337       var values = [];
    338       for (var i = 0; i < ctr.numSeries; ++i)
    339         values.push(ctr.samples[ctr.numSeries * sampleIndex + i]);
    340 
    341       var table = results.appendTable('analysis-counter-table', 2);
    342       results.appendTableHeader(table, 'Selected counter:');
    343       results.appendSummaryRow(table, 'Title', ctr.name);
    344       results.appendSummaryRowTime(
    345           table, 'Timestamp', ctr.timestamps[sampleIndex]);
    346 
    347       for (var i = 0; i < ctr.numSeries; i++)
    348         results.appendSummaryRow(table, ctr.seriesNames[i], values[i]);
    349     } else if (counterSampleHits.length > 1) {
    350       var hitsByCounter = {};
    351       for (var i = 0; i < counterSampleHits.length; i++) {
    352         var ctr = counterSampleHits[i].counter;
    353         if (!hitsByCounter[ctr.guid])
    354           hitsByCounter[ctr.guid] = [];
    355         hitsByCounter[ctr.guid].push(counterSampleHits[i]);
    356       }
    357 
    358       var table = results.appendTable('analysis-counter-table', 7);
    359       results.appendTableHeader(table, 'Counters:');
    360       for (var id in hitsByCounter) {
    361         var hits = hitsByCounter[id];
    362         var ctr = hits[0].counter;
    363         var sampleIndices = [];
    364         for (var i = 0; i < hits.length; i++)
    365           sampleIndices.push(hits[i].sampleIndex);
    366 
    367         var stats = ctr.getSampleStatistics(sampleIndices);
    368         for (var i = 0; i < stats.length; i++) {
    369           results.appendDataRow(
    370               table, ctr.name + ': ' + ctr.seriesNames[i], undefined,
    371               undefined, stats[i]);
    372         }
    373       }
    374     }
    375   }
    376 
    377   var TimelineAnalysisView = tracing.ui.define('div');
    378 
    379   TimelineAnalysisView.prototype = {
    380     __proto__: HTMLDivElement.prototype,
    381 
    382     decorate: function() {
    383       this.className = 'analysis';
    384     },
    385 
    386     set selection(selection) {
    387       this.textContent = '';
    388       var results = new AnalysisResults();
    389       analyzeSelection(results, selection);
    390       this.appendChild(results);
    391     }
    392   };
    393 
    394   return {
    395     TimelineAnalysisView: TimelineAnalysisView,
    396     analyzeSelection_: analyzeSelection
    397   };
    398 });
    399