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 TraceModel 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 */ 23base.require('base.range'); 24base.require('base.events'); 25base.require('tracing.trace_model.process'); 26base.require('tracing.trace_model.kernel'); 27base.require('tracing.filter'); 28 29base.exportTo('tracing', function() { 30 31 var Process = tracing.trace_model.Process; 32 var Kernel = tracing.trace_model.Kernel; 33 34 /** 35 * Builds a model from an array of TraceEvent objects. 36 * @param {Object=} opt_eventData Data from a single trace to be imported into 37 * the new model. See TraceModel.importTraces for details on how to 38 * import multiple traces at once. 39 * @param {bool=} opt_shiftWorldToZero Whether to shift the world to zero. 40 * Defaults to true. 41 * @constructor 42 */ 43 function TraceModel(opt_eventData, opt_shiftWorldToZero) { 44 this.kernel = new Kernel(this); 45 this.processes = {}; 46 this.importErrors = []; 47 this.metadata = []; 48 this.categories = []; 49 this.bounds = new base.Range(); 50 this.instantEvents = []; 51 52 if (opt_eventData) 53 this.importTraces([opt_eventData], opt_shiftWorldToZero); 54 } 55 56 TraceModel.importerConstructors_ = []; 57 58 /** 59 * Registers an importer. All registered importers are considered 60 * when processing an import request. 61 * 62 * @param {Function} importerConstructor The importer's constructor function. 63 */ 64 TraceModel.registerImporter = function(importerConstructor) { 65 TraceModel.importerConstructors_.push(importerConstructor); 66 }; 67 68 TraceModel.prototype = { 69 __proto__: base.EventTarget.prototype, 70 71 get numProcesses() { 72 var n = 0; 73 for (var p in this.processes) 74 n++; 75 return n; 76 }, 77 78 /** 79 * @return {Process} Gets a TimlineProcess for a specified pid or 80 * creates one if it does not exist. 81 */ 82 getOrCreateProcess: function(pid) { 83 if (!this.processes[pid]) 84 this.processes[pid] = new Process(this, pid); 85 return this.processes[pid]; 86 }, 87 88 pushInstantEvent: function(instantEvent) { 89 this.instantEvents.push(instantEvent); 90 }, 91 92 /** 93 * Generates the set of categories from the slices and counters. 94 */ 95 updateCategories_: function() { 96 var categoriesDict = {}; 97 this.kernel.addCategoriesToDict(categoriesDict); 98 for (var pid in this.processes) 99 this.processes[pid].addCategoriesToDict(categoriesDict); 100 101 this.categories = []; 102 for (var category in categoriesDict) 103 if (category != '') 104 this.categories.push(category); 105 }, 106 107 updateBounds: function() { 108 this.bounds.reset(); 109 110 this.kernel.updateBounds(); 111 this.bounds.addRange(this.kernel.bounds); 112 113 for (var pid in this.processes) { 114 this.processes[pid].updateBounds(); 115 this.bounds.addRange(this.processes[pid].bounds); 116 } 117 }, 118 119 shiftWorldToZero: function() { 120 if (this.bounds.isEmpty) 121 return; 122 var timeBase = this.bounds.min; 123 this.kernel.shiftTimestampsForward(-timeBase); 124 for (var id in this.instantEvents) 125 this.instantEvents[id].start -= timeBase; 126 for (var pid in this.processes) 127 this.processes[pid].shiftTimestampsForward(-timeBase); 128 this.updateBounds(); 129 }, 130 131 getAllThreads: function() { 132 var threads = []; 133 for (var tid in this.kernel.threads) { 134 threads.push(process.threads[tid]); 135 } 136 for (var pid in this.processes) { 137 var process = this.processes[pid]; 138 for (var tid in process.threads) { 139 threads.push(process.threads[tid]); 140 } 141 } 142 return threads; 143 }, 144 145 /** 146 * @return {Array} An array of all processes in the model. 147 */ 148 getAllProcesses: function() { 149 var processes = []; 150 for (var pid in this.processes) 151 processes.push(this.processes[pid]); 152 return processes; 153 }, 154 155 /** 156 * @return {Array} An array of all the counters in the model. 157 */ 158 getAllCounters: function() { 159 var counters = []; 160 counters.push.apply( 161 counters, base.dictionaryValues(this.kernel.counters)); 162 for (var pid in this.processes) { 163 var process = this.processes[pid]; 164 for (var tid in process.counters) { 165 counters.push(process.counters[tid]); 166 } 167 } 168 return counters; 169 }, 170 171 /** 172 * @param {String} The name of the thread to find. 173 * @return {Array} An array of all the matched threads. 174 */ 175 findAllThreadsNamed: function(name) { 176 var namedThreads = []; 177 namedThreads.push.apply( 178 namedThreads, 179 this.kernel.findAllThreadsNamed(name)); 180 for (var pid in this.processes) { 181 namedThreads.push.apply( 182 namedThreads, 183 this.processes[pid].findAllThreadsNamed(name)); 184 } 185 return namedThreads; 186 }, 187 188 createImporter_: function(eventData) { 189 var importerConstructor; 190 for (var i = 0; i < TraceModel.importerConstructors_.length; ++i) { 191 if (TraceModel.importerConstructors_[i].canImport(eventData)) { 192 importerConstructor = TraceModel.importerConstructors_[i]; 193 break; 194 } 195 } 196 if (!importerConstructor) 197 throw new Error( 198 'Could not find an importer for the provided eventData.'); 199 200 var importer = new importerConstructor( 201 this, eventData); 202 return importer; 203 }, 204 205 /** 206 * Imports the provided traces into the model. The eventData type 207 * is undefined and will be passed to all the importers registered 208 * via TraceModel.registerImporter. The first importer that returns true 209 * for canImport(events) will be used to import the events. 210 * 211 * The primary trace is provided via the eventData variable. If multiple 212 * traces are to be imported, specify the first one as events, and the 213 * remainder in the opt_additionalEventData array. 214 * 215 * @param {Array} traces An array of eventData to be imported. Each 216 * eventData should correspond to a single trace file and will be handled by 217 * a separate importer. 218 * @param {bool=} opt_shiftWorldToZero Whether to shift the world to zero. 219 * Defaults to true. 220 * @param {bool=} opt_pruneEmptyContainers Whether to prune empty 221 * containers. Defaults to true. 222 */ 223 importTraces: function(traces, 224 opt_shiftWorldToZero, 225 opt_pruneEmptyContainers) { 226 if (opt_shiftWorldToZero === undefined) 227 opt_shiftWorldToZero = true; 228 if (opt_pruneEmptyContainers === undefined) 229 opt_pruneEmptyContainers = true; 230 231 // Copy the traces array, we may mutate it. 232 traces = traces.slice(0); 233 234 // Figure out which importers to use. 235 var importers = []; 236 for (var i = 0; i < traces.length; ++i) 237 importers.push(this.createImporter_(traces[i])); 238 239 // Some traces have other traces inside them. Before doing the full 240 // import, ask the importer if it has any subtraces, and if so, create an 241 // importer for that, also. 242 for (var i = 0; i < importers.length; i++) { 243 var subTrace = importers[i].extractSubtrace(); 244 if (!subTrace) 245 continue; 246 traces.push(subTrace); 247 importers.push(this.createImporter_(subTrace)); 248 } 249 250 // Sort them on priority. This ensures importing happens in a predictable 251 // order, e.g. linux_perf_importer before trace_event_importer. 252 importers.sort(function(x, y) { 253 return x.importPriority - y.importPriority; 254 }); 255 256 // Run the import. 257 for (var i = 0; i < importers.length; i++) 258 importers[i].importEvents(i > 0); 259 260 // Autoclose open slices. 261 this.updateBounds(); 262 this.kernel.autoCloseOpenSlices(this.bounds.max); 263 for (var pid in this.processes) 264 this.processes[pid].autoCloseOpenSlices(this.bounds.max); 265 266 // Finalize import. 267 for (var i = 0; i < importers.length; i++) 268 importers[i].finalizeImport(); 269 270 // Run preinit. 271 for (var pid in this.processes) 272 this.processes[pid].preInitializeObjects(); 273 274 // Prune empty containers. 275 if (opt_pruneEmptyContainers) { 276 this.kernel.pruneEmptyContainers(); 277 for (var pid in this.processes) { 278 this.processes[pid].pruneEmptyContainers(); 279 } 280 } 281 282 // Merge kernel and userland slices on each thread. 283 for (var pid in this.processes) { 284 this.processes[pid].mergeKernelWithUserland(); 285 } 286 287 this.updateBounds(); 288 289 this.updateCategories_(); 290 291 if (opt_shiftWorldToZero) 292 this.shiftWorldToZero(); 293 294 // Join refs. 295 for (var i = 0; i < importers.length; i++) 296 importers[i].joinRefs(); 297 298 // Delete any undeleted objects. 299 for (var pid in this.processes) 300 this.processes[pid].autoDeleteObjects(this.bounds.max); 301 302 // Run initializers. 303 for (var pid in this.processes) 304 this.processes[pid].initializeObjects(); 305 } 306 }; 307 308 /** 309 * Importer for empty strings and arrays. 310 * @constructor 311 */ 312 function TraceModelEmptyImporter(events) { 313 this.importPriority = 0; 314 }; 315 316 TraceModelEmptyImporter.canImport = function(eventData) { 317 if (eventData instanceof Array && eventData.length == 0) 318 return true; 319 if (typeof(eventData) === 'string' || eventData instanceof String) { 320 return eventData.length == 0; 321 } 322 return false; 323 }; 324 325 TraceModelEmptyImporter.prototype = { 326 __proto__: Object.prototype, 327 328 extractSubtrace: function() { 329 return undefined; 330 }, 331 importEvents: function() { 332 }, 333 finalizeImport: function() { 334 }, 335 joinRefs: function() { 336 } 337 }; 338 339 TraceModel.registerImporter(TraceModelEmptyImporter); 340 341 return { 342 TraceModel: TraceModel 343 }; 344}); 345