// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. 'use strict'; /** * @fileoverview Model is a parsed representation of the * TraceEvents obtained from base/trace_event in which the begin-end * tokens are converted into a hierarchy of processes, threads, * subrows, and slices. * * The building block of the model is a slice. A slice is roughly * equivalent to function call executing on a specific thread. As a * result, slices may have one or more subslices. * * A thread contains one or more subrows of slices. Row 0 corresponds to * the "root" slices, e.g. the topmost slices. Row 1 contains slices that * are nested 1 deep in the stack, and so on. We use these subrows to draw * nesting tasks. * */ base.require('range'); base.require('event_target'); base.require('model.process'); base.require('model.kernel'); base.require('model.cpu'); base.require('filter'); base.exportTo('tracing', function() { var Process = tracing.model.Process; var Kernel = tracing.model.Kernel; var Cpu = tracing.model.Cpu; /** * Builds a model from an array of TraceEvent objects. * @param {Object=} opt_eventData Data from a single trace to be imported into * the new model. See Model.importTraces for details on how to * import multiple traces at once. * @param {bool=} opt_shiftWorldToZero Whether to shift the world to zero. * Defaults to true. * @constructor */ function Model(opt_eventData, opt_shiftWorldToZero) { this.kernel = new Kernel(); this.cpus = {}; this.processes = {}; this.importErrors = []; this.metadata = []; this.categories = []; this.bounds = new base.Range(); if (opt_eventData) this.importTraces([opt_eventData], opt_shiftWorldToZero); } var importerConstructors = []; /** * Registers an importer. All registered importers are considered * when processing an import request. * * @param {Function} importerConstructor The importer's constructor function. */ Model.registerImporter = function(importerConstructor) { importerConstructors.push(importerConstructor); }; Model.prototype = { __proto__: base.EventTarget.prototype, get numProcesses() { var n = 0; for (var p in this.processes) n++; return n; }, /** * @return {Cpu} Gets a specific Cpu or creates one if * it does not exist. */ getOrCreateCpu: function(cpuNumber) { if (!this.cpus[cpuNumber]) this.cpus[cpuNumber] = new Cpu(cpuNumber); return this.cpus[cpuNumber]; }, /** * @return {Process} Gets a TimlineProcess for a specified pid or * creates one if it does not exist. */ getOrCreateProcess: function(pid) { if (!this.processes[pid]) this.processes[pid] = new Process(pid); return this.processes[pid]; }, /** * Generates the set of categories from the slices and counters. */ updateCategories_: function() { var categoriesDict = {}; this.kernel.addCategoriesToDict(categoriesDict); for (var pid in this.processes) this.processes[pid].addCategoriesToDict(categoriesDict); for (var cpuNumber in this.cpus) this.cpus[cpuNumber].addCategoriesToDict(categoriesDict); this.categories = []; for (var category in categoriesDict) if (category != '') this.categories.push(category); }, updateBounds: function() { this.bounds.reset(); this.kernel.updateBounds(); this.bounds.addRange(this.kernel.bounds); for (var pid in this.processes) { this.processes[pid].updateBounds(); this.bounds.addRange(this.processes[pid].bounds); } for (var cpuNumber in this.cpus) { this.cpus[cpuNumber].updateBounds(); this.bounds.addRange(this.cpus[cpuNumber].bounds); } }, shiftWorldToZero: function() { if (this.bounds.isEmpty) return; var timeBase = this.bounds.min; this.kernel.shiftTimestampsForward(-timeBase); for (var pid in this.processes) this.processes[pid].shiftTimestampsForward(-timeBase); for (var cpuNumber in this.cpus) this.cpus[cpuNumber].shiftTimestampsForward(-timeBase); this.updateBounds(); }, getAllThreads: function() { var threads = []; for (var tid in this.kernel.threads) { threads.push(process.threads[tid]); } for (var pid in this.processes) { var process = this.processes[pid]; for (var tid in process.threads) { threads.push(process.threads[tid]); } } return threads; }, /** * @return {Array} An array of all cpus in the model. */ getAllCpus: function() { var cpus = []; for (var cpu in this.cpus) cpus.push(this.cpus[cpu]); return cpus; }, /** * @return {Array} An array of all processes in the model. */ getAllProcesses: function() { var processes = []; for (var pid in this.processes) processes.push(this.processes[pid]); return processes; }, /** * @return {Array} An array of all the counters in the model. */ getAllCounters: function() { var counters = []; for (var pid in this.processes) { var process = this.processes[pid]; for (var tid in process.counters) { counters.push(process.counters[tid]); } } for (var cpuNumber in this.cpus) { var cpu = this.cpus[cpuNumber]; for (var counterName in cpu.counters) counters.push(cpu.counters[counterName]); } return counters; }, /** * @param {String} The name of the thread to find. * @return {Array} An array of all the matched threads. */ findAllThreadsNamed: function(name) { var namedThreads = []; namedThreads.push.apply( namedThreads, this.kernel.findAllThreadsNamed(name)); for (var pid in this.processes) { namedThreads.push.apply( namedThreads, this.processes[pid].findAllThreadsNamed(name)); } return namedThreads; }, createImporter_: function(eventData) { var importerConstructor; for (var i = 0; i < importerConstructors.length; ++i) { if (importerConstructors[i].canImport(eventData)) { importerConstructor = importerConstructors[i]; break; } } if (!importerConstructor) throw new Error( 'Could not find an importer for the provided eventData.'); var importer = new importerConstructor( this, eventData); return importer; }, /** * Imports the provided traces into the model. The eventData type * is undefined and will be passed to all the importers registered * via Model.registerImporter. The first importer that returns true * for canImport(events) will be used to import the events. * * The primary trace is provided via the eventData variable. If multiple * traces are to be imported, specify the first one as events, and the * remainder in the opt_additionalEventData array. * * @param {Array} traces An array of eventData to be imported. Each * eventData should correspond to a single trace file and will be handled by * a separate importer. * @param {bool=} opt_shiftWorldToZero Whether to shift the world to zero. * Defaults to true. */ importTraces: function(traces, opt_shiftWorldToZero) { if (opt_shiftWorldToZero === undefined) opt_shiftWorldToZero = true; // Figure out which importers to use. var importers = []; for (var i = 0; i < traces.length; ++i) importers.push(this.createImporter_(traces[i])); // Sort them on priority. This ensures importing happens in a predictable // order, e.g. linux_perf_importer before trace_event_importer. importers.sort(function(x, y) { return x.importPriority - y.importPriority; }); // Run the import. for (var i = 0; i < importers.length; i++) importers[i].importEvents(i > 0); // Autoclose open slices. this.updateBounds(); this.kernel.autoCloseOpenSlices(this.bounds.max); for (var pid in this.processes) { this.processes[pid].autoCloseOpenSlices(this.bounds.max); } // Finalize import. for (var i = 0; i < importers.length; i++) importers[i].finalizeImport(); // Prune empty containers. this.kernel.pruneEmptyContainers(); for (var pid in this.processes) { this.processes[pid].pruneEmptyContainers(); } // Merge kernel and userland slices on each thread. for (var pid in this.processes) { this.processes[pid].mergeKernelWithUserland(); } this.updateBounds(); this.updateCategories_(); if (opt_shiftWorldToZero) this.shiftWorldToZero(); } }; /** * Importer for empty strings and arrays. * @constructor */ function ModelEmptyImporter(events) { this.importPriority = 0; }; ModelEmptyImporter.canImport = function(eventData) { if (eventData instanceof Array && eventData.length == 0) return true; if (typeof(eventData) === 'string' || eventData instanceof String) { return eventData.length == 0; } return false; }; ModelEmptyImporter.prototype = { __proto__: Object.prototype, importEvents: function() { }, finalizeImport: function() { } }; Model.registerImporter(ModelEmptyImporter); return { Model: Model }; });