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