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 TimelineModel is a parsed representation of the 9 * TraceEvents obtained from base/trace_event in which the begin-end 10 * tokens are converted into a hierarchy of processes, threads, 11 * subrows, and slices. 12 * 13 * The building block of the model is a slice. A slice is roughly 14 * equivalent to function call executing on a specific thread. As a 15 * result, slices may have one or more subslices. 16 * 17 * A thread contains one or more subrows of slices. Row 0 corresponds to 18 * the "root" slices, e.g. the topmost slices. Row 1 contains slices that 19 * are nested 1 deep in the stack, and so on. We use these subrows to draw 20 * nesting tasks. 21 * 22 */ 23 base.require('event_target'); 24 base.require('timeline_process'); 25 base.require('timeline_cpu'); 26 base.require('timeline_filter'); 27 base.exportTo('tracing', function() { 28 29 var TimelineProcess = tracing.TimelineProcess; 30 var TimelineCpu = tracing.TimelineCpu; 31 32 /** 33 * Builds a model from an array of TraceEvent objects. 34 * @param {Object=} opt_eventData Data from a single trace to be imported into 35 * the new model. See TimelineModel.importTraces for details on how to 36 * import multiple traces at once. 37 * @param {bool=} opt_shiftWorldToZero Whether to shift the world to zero. 38 * Defaults to true. 39 * @constructor 40 */ 41 function TimelineModel(opt_eventData, opt_shiftWorldToZero) { 42 this.cpus = {}; 43 this.processes = {}; 44 this.importErrors = []; 45 this.metadata = []; 46 this.categories = []; 47 48 if (opt_eventData) 49 this.importTraces([opt_eventData], opt_shiftWorldToZero); 50 } 51 52 var importerConstructors = []; 53 54 /** 55 * Registers an importer. All registered importers are considered 56 * when processing an import request. 57 * 58 * @param {Function} importerConstructor The importer's constructor function. 59 */ 60 TimelineModel.registerImporter = function(importerConstructor) { 61 importerConstructors.push(importerConstructor); 62 }; 63 64 function TimelineModelEmptyImporter(events) { 65 this.importPriority = 0; 66 }; 67 68 TimelineModelEmptyImporter.canImport = function(eventData) { 69 if (eventData instanceof Array && eventData.length == 0) 70 return true; 71 if (typeof(eventData) === 'string' || eventData instanceof String) { 72 return eventData.length == 0; 73 } 74 return false; 75 }; 76 77 TimelineModelEmptyImporter.prototype = { 78 __proto__: Object.prototype, 79 80 importEvents: function() { 81 }, 82 finalizeImport: function() { 83 } 84 }; 85 86 TimelineModel.registerImporter(TimelineModelEmptyImporter); 87 88 TimelineModel.prototype = { 89 __proto__: base.EventTarget.prototype, 90 91 get numProcesses() { 92 var n = 0; 93 for (var p in this.processes) 94 n++; 95 return n; 96 }, 97 98 /** 99 * @return {TimelineProcess} Gets a specific TimelineCpu or creates one if 100 * it does not exist. 101 */ 102 getOrCreateCpu: function(cpuNumber) { 103 if (!this.cpus[cpuNumber]) 104 this.cpus[cpuNumber] = new TimelineCpu(cpuNumber); 105 return this.cpus[cpuNumber]; 106 }, 107 108 /** 109 * @return {TimelineProcess} Gets a TimlineProcess for a specified pid or 110 * creates one if it does not exist. 111 */ 112 getOrCreateProcess: function(pid) { 113 if (!this.processes[pid]) 114 this.processes[pid] = new TimelineProcess(pid); 115 return this.processes[pid]; 116 }, 117 118 /** 119 * Closes any slices that need closing 120 */ 121 autoCloseOpenSlices_: function() { 122 this.updateBounds(); 123 var maxTimestamp = this.maxTimestamp; 124 for (var pid in this.processes) { 125 var process = this.processes[pid]; 126 for (var tid in process.threads) { 127 var thread = process.threads[tid]; 128 thread.autoCloseOpenSlices(maxTimestamp); 129 } 130 } 131 }, 132 133 /** 134 * Generates the set of categories from the slices. 135 */ 136 updateCategories_: function() { 137 // TODO(sullivan): Is there a way to do this more cleanly? 138 for (var pid in this.processes) { 139 var process = this.processes[pid]; 140 for (var tid in process.threads) { 141 var slices = process.threads[tid].slices; 142 for (var i = 0; i < slices.length; i++) { 143 var category = slices[i].category; 144 if (category && this.categories.indexOf(category) == -1) { 145 this.categories.push(category); 146 } 147 } 148 } 149 } 150 for (var cpu in this.cpus) { 151 var slices = this.cpus[cpu].slices; 152 for (var i = 0; i < slices.length; i++) { 153 var category = slices[i].category; 154 if (category && this.categories.indexOf(category) == -1) { 155 this.categories.push(category); 156 } 157 } 158 } 159 }, 160 161 /** 162 * Removes threads from the model that are fully empty. 163 */ 164 pruneEmptyThreads_: function() { 165 for (var pid in this.processes) { 166 var process = this.processes[pid]; 167 var threadsToKeep = {}; 168 for (var tid in process.threads) { 169 var thread = process.threads[tid]; 170 if (!thread.isEmpty) 171 threadsToKeep[tid] = thread; 172 } 173 process.threads = threadsToKeep; 174 } 175 }, 176 177 updateBounds: function() { 178 var wmin = Infinity; 179 var wmax = -wmin; 180 var hasData = false; 181 182 var threads = this.getAllThreads(); 183 for (var tI = 0; tI < threads.length; tI++) { 184 var thread = threads[tI]; 185 thread.updateBounds(); 186 if (thread.minTimestamp != undefined && 187 thread.maxTimestamp != undefined) { 188 wmin = Math.min(wmin, thread.minTimestamp); 189 wmax = Math.max(wmax, thread.maxTimestamp); 190 hasData = true; 191 } 192 } 193 var counters = this.getAllCounters(); 194 for (var tI = 0; tI < counters.length; tI++) { 195 var counter = counters[tI]; 196 counter.updateBounds(); 197 if (counter.minTimestamp != undefined && 198 counter.maxTimestamp != undefined) { 199 hasData = true; 200 wmin = Math.min(wmin, counter.minTimestamp); 201 wmax = Math.max(wmax, counter.maxTimestamp); 202 } 203 } 204 205 for (var cpuNumber in this.cpus) { 206 var cpu = this.cpus[cpuNumber]; 207 cpu.updateBounds(); 208 if (cpu.minTimestamp != undefined && 209 cpu.maxTimestamp != undefined) { 210 hasData = true; 211 wmin = Math.min(wmin, cpu.minTimestamp); 212 wmax = Math.max(wmax, cpu.maxTimestamp); 213 } 214 } 215 216 if (hasData) { 217 this.minTimestamp = wmin; 218 this.maxTimestamp = wmax; 219 } else { 220 this.maxTimestamp = undefined; 221 this.minTimestamp = undefined; 222 } 223 }, 224 225 shiftWorldToZero: function() { 226 if (this.minTimestamp === undefined) 227 return; 228 var timeBase = this.minTimestamp; 229 for (var pid in this.processes) 230 this.processes[pid].shiftTimestampsForward(-timeBase); 231 for (var cpuNumber in this.cpus) 232 this.cpus[cpuNumber].shiftTimestampsForward(-timeBase); 233 this.updateBounds(); 234 }, 235 236 getAllThreads: function() { 237 var threads = []; 238 for (var pid in this.processes) { 239 var process = this.processes[pid]; 240 for (var tid in process.threads) { 241 threads.push(process.threads[tid]); 242 } 243 } 244 return threads; 245 }, 246 247 /** 248 * @return {Array} An array of all cpus in the model. 249 */ 250 getAllCpus: function() { 251 var cpus = []; 252 for (var cpu in this.cpus) 253 cpus.push(this.cpus[cpu]); 254 return cpus; 255 }, 256 257 /** 258 * @return {Array} An array of all processes in the model. 259 */ 260 getAllProcesses: function() { 261 var processes = []; 262 for (var pid in this.processes) 263 processes.push(this.processes[pid]); 264 return processes; 265 }, 266 267 /** 268 * @return {Array} An array of all the counters in the model. 269 */ 270 getAllCounters: function() { 271 var counters = []; 272 for (var pid in this.processes) { 273 var process = this.processes[pid]; 274 for (var tid in process.counters) { 275 counters.push(process.counters[tid]); 276 } 277 } 278 for (var cpuNumber in this.cpus) { 279 var cpu = this.cpus[cpuNumber]; 280 for (var counterName in cpu.counters) 281 counters.push(cpu.counters[counterName]); 282 } 283 return counters; 284 }, 285 286 /** 287 * @param {String} The name of the thread to find. 288 * @return {Array} An array of all the matched threads. 289 */ 290 findAllThreadsNamed: function(name) { 291 var namedThreads = []; 292 var threads = this.getAllThreads(); 293 for (var i = 0; i < threads.length; i++) { 294 var thread = threads[i]; 295 if (thread.name == name) 296 namedThreads.push(thread); 297 } 298 return namedThreads; 299 }, 300 301 createImporter_: function(eventData) { 302 var importerConstructor; 303 for (var i = 0; i < importerConstructors.length; ++i) { 304 if (importerConstructors[i].canImport(eventData)) { 305 importerConstructor = importerConstructors[i]; 306 break; 307 } 308 } 309 if (!importerConstructor) 310 throw new Error( 311 'Could not find an importer for the provided eventData.'); 312 313 var importer = new importerConstructor( 314 this, eventData); 315 return importer; 316 }, 317 318 /** 319 * Imports the provided traces into the model. The eventData type 320 * is undefined and will be passed to all the timeline importers registered 321 * via TimelineModel.registerImporter. The first importer that returns true 322 * for canImport(events) will be used to import the events. 323 * 324 * The primary trace is provided via the eventData variable. If multiple 325 * traces are to be imported, specify the first one as events, and the 326 * remainder in the opt_additionalEventData array. 327 * 328 * @param {Array} traces An array of eventData to be imported. Each 329 * eventData should correspond to a single trace file and will be handled by 330 * a separate importer. 331 * @param {bool=} opt_shiftWorldToZero Whether to shift the world to zero. 332 * Defaults to true. 333 */ 334 importTraces: function(traces, 335 opt_shiftWorldToZero) { 336 if (opt_shiftWorldToZero === undefined) 337 opt_shiftWorldToZero = true; 338 339 // Figure out which importers to use. 340 var importers = []; 341 for (var i = 0; i < traces.length; ++i) 342 importers.push(this.createImporter_(traces[i])); 343 344 // Sort them on priority. This ensures importing happens in a predictable 345 // order, e.g. linux_perf_importer before trace_event_importer. 346 importers.sort(function(x, y) { 347 return x.importPriority - y.importPriority; 348 }); 349 350 // Run the import. 351 for (var i = 0; i < importers.length; i++) 352 importers[i].importEvents(i > 0); 353 354 this.autoCloseOpenSlices_(); 355 356 for (var i = 0; i < importers.length; i++) 357 importers[i].finalizeImport(); 358 359 this.pruneEmptyThreads_(); 360 this.updateBounds(); 361 362 this.updateCategories_(); 363 364 if (opt_shiftWorldToZero) 365 this.shiftWorldToZero(); 366 } 367 }; 368 369 return { 370 TimelineModel: TimelineModel 371 }; 372 373 }); 374