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