| <!DOCTYPE html> |
| <!-- |
| 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. |
| --> |
| |
| <link rel="import" href="/tracing/base/base.html"> |
| <link rel="import" href="/tracing/base/event.html"> |
| <link rel="import" href="/tracing/base/interval_tree.html"> |
| <link rel="import" href="/tracing/base/range.html"> |
| <link rel="import" href="/tracing/base/task.html"> |
| <link rel="import" href="/tracing/base/units/units.html"> |
| <link rel="import" href="/tracing/core/auditor.html"> |
| <link rel="import" href="/tracing/core/filter.html"> |
| <link rel="import" href="/tracing/model/alert.html"> |
| <link rel="import" href="/tracing/model/device.html"> |
| <link rel="import" href="/tracing/model/flow_event.html"> |
| <link rel="import" href="/tracing/model/frame.html"> |
| <link rel="import" href="/tracing/model/global_memory_dump.html"> |
| <link rel="import" href="/tracing/model/instant_event.html"> |
| <link rel="import" href="/tracing/model/interaction_record.html"> |
| <link rel="import" href="/tracing/model/kernel.html"> |
| <link rel="import" href="/tracing/model/model_indices.html"> |
| <link rel="import" href="/tracing/model/process.html"> |
| <link rel="import" href="/tracing/model/process_memory_dump.html"> |
| <link rel="import" href="/tracing/model/sample.html"> |
| <link rel="import" href="/tracing/model/stack_frame.html"> |
| <link rel="import" href="/tracing/ui/base/overlay.html"> |
| |
| <script> |
| '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. |
| * |
| */ |
| tr.exportTo('tr', function() { |
| var Process = tr.model.Process; |
| var Device = tr.model.Device; |
| var Kernel = tr.model.Kernel; |
| var GlobalMemoryDump = tr.model.GlobalMemoryDump; |
| var GlobalInstantEvent = tr.model.GlobalInstantEvent; |
| var FlowEvent = tr.model.FlowEvent; |
| var Alert = tr.model.Alert; |
| var InteractionRecord = tr.model.InteractionRecord; |
| var Sample = tr.model.Sample; |
| |
| function ClockSyncRecord(name, ts, args) { |
| this.name = name; |
| this.ts = ts; |
| this.args = args; |
| } |
| |
| /** |
| * @constructor |
| */ |
| function Model() { |
| tr.model.EventContainer.call(this); |
| tr.b.EventTarget.decorate(this); |
| |
| this.timestampShiftToZeroAmount_ = 0; |
| |
| this.faviconHue = 'blue'; // Should be a key from favicons.html |
| |
| this.device = new Device(this); |
| this.kernel = new Kernel(this); |
| this.processes = {}; |
| this.metadata = []; |
| this.categories = []; |
| this.instantEvents = []; |
| this.flowEvents = []; |
| this.clockSyncRecords = []; |
| this.intrinsicTimeUnit_ = undefined; |
| |
| this.stackFrames = {}; |
| this.samples = []; |
| |
| this.alerts = []; |
| this.interactionRecords = []; |
| |
| this.flowIntervalTree = new tr.b.IntervalTree( |
| function(f) { return f.start; }, |
| function(f) { return f.end; }); |
| |
| this.globalMemoryDumps = []; |
| |
| this.userFriendlyCategoryDrivers_ = []; |
| |
| this.annotationsByGuid_ = {}; |
| this.modelIndices = undefined; |
| |
| this.importWarnings_ = []; |
| this.reportedImportWarnings_ = {}; |
| |
| this.isTimeHighResolution_ = undefined; |
| } |
| |
| Model.prototype = { |
| __proto__: tr.model.EventContainer.prototype, |
| |
| iterateAllEventsInThisContainer: function(eventTypePredicate, |
| callback, opt_this) { |
| if (eventTypePredicate.call(opt_this, GlobalMemoryDump)) |
| this.globalMemoryDumps.forEach(callback, opt_this); |
| |
| if (eventTypePredicate.call(opt_this, GlobalInstantEvent)) |
| this.instantEvents.forEach(callback, opt_this); |
| |
| if (eventTypePredicate.call(opt_this, FlowEvent)) |
| this.flowEvents.forEach(callback, opt_this); |
| |
| if (eventTypePredicate.call(opt_this, Alert)) |
| this.alerts.forEach(callback, opt_this); |
| |
| if (eventTypePredicate.call(opt_this, InteractionRecord)) |
| this.interactionRecords.forEach(callback, opt_this); |
| |
| if (eventTypePredicate.call(opt_this, Sample)) |
| this.samples.forEach(callback, opt_this); |
| }, |
| |
| iterateAllChildEventContainers: function(callback, opt_this) { |
| callback.call(opt_this, this.device); |
| callback.call(opt_this, this.kernel); |
| for (var pid in this.processes) |
| callback.call(opt_this, this.processes[pid]); |
| }, |
| |
| /** |
| * Some objects in the model can persist their state in ModelSettings. |
| * |
| * This iterates through them. |
| */ |
| iterateAllPersistableObjects: function(callback) { |
| this.kernel.iterateAllPersistableObjects(callback); |
| for (var pid in this.processes) |
| this.processes[pid].iterateAllPersistableObjects(callback); |
| }, |
| |
| updateBounds: function() { |
| this.bounds.reset(); |
| var bounds = this.bounds; |
| |
| this.iterateAllChildEventContainers(function(ec) { |
| ec.updateBounds(); |
| bounds.addRange(ec.bounds); |
| }); |
| this.iterateAllEventsInThisContainer( |
| function(eventConstructor) { return true; }, |
| function(event) { |
| event.addBoundsToRange(bounds); |
| }); |
| }, |
| |
| shiftWorldToZero: function() { |
| var shiftAmount = -this.bounds.min; |
| this.timestampShiftToZeroAmount_ = shiftAmount; |
| this.iterateAllChildEventContainers(function(ec) { |
| ec.shiftTimestampsForward(shiftAmount); |
| }); |
| this.iterateAllEventsInThisContainer( |
| function(eventConstructor) { return true; }, |
| function(event) { |
| event.start += shiftAmount; |
| }); |
| this.updateBounds(); |
| }, |
| |
| convertTimestampToModelTime: function(sourceClockDomainName, ts) { |
| if (sourceClockDomainName !== 'traceEventClock') |
| throw new Error('Only traceEventClock is supported.'); |
| return tr.b.u.Units.timestampFromUs(ts) + |
| this.timestampShiftToZeroAmount_; |
| }, |
| |
| get numProcesses() { |
| var n = 0; |
| for (var p in this.processes) |
| n++; |
| return n; |
| }, |
| |
| /** |
| * @return {Process} Gets a TimelineProcess for a specified pid. Returns |
| * undefined if the process doesn't exist. |
| */ |
| getProcess: function(pid) { |
| return this.processes[pid]; |
| }, |
| |
| /** |
| * @return {Process} Gets a TimelineProcess for a specified pid or |
| * creates one if it does not exist. |
| */ |
| getOrCreateProcess: function(pid) { |
| if (!this.processes[pid]) |
| this.processes[pid] = new Process(this, pid); |
| return this.processes[pid]; |
| }, |
| |
| pushInstantEvent: function(instantEvent) { |
| this.instantEvents.push(instantEvent); |
| }, |
| |
| addStackFrame: function(stackFrame) { |
| if (this.stackFrames[stackFrame.id]) |
| throw new Error('Stack frame already exists'); |
| this.stackFrames[stackFrame.id] = stackFrame; |
| return stackFrame; |
| }, |
| |
| addInteractionRecord: function(ir) { |
| this.interactionRecords.push(ir); |
| return ir; |
| }, |
| |
| getClockSyncRecordsNamed: function(name) { |
| return this.clockSyncRecords.filter(function(x) { |
| return x.name === name; |
| }); |
| }, |
| |
| /** |
| * Generates the set of categories from the slices and counters. |
| */ |
| updateCategories_: function() { |
| var categoriesDict = {}; |
| this.device.addCategoriesToDict(categoriesDict); |
| this.kernel.addCategoriesToDict(categoriesDict); |
| for (var pid in this.processes) |
| this.processes[pid].addCategoriesToDict(categoriesDict); |
| |
| this.categories = []; |
| for (var category in categoriesDict) |
| if (category != '') |
| this.categories.push(category); |
| }, |
| |
| 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 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 = []; |
| counters.push.apply( |
| counters, tr.b.dictionaryValues(this.device.counters)); |
| counters.push.apply( |
| counters, tr.b.dictionaryValues(this.kernel.counters)); |
| for (var pid in this.processes) { |
| var process = this.processes[pid]; |
| for (var tid in process.counters) { |
| counters.push(process.counters[tid]); |
| } |
| } |
| return counters; |
| }, |
| |
| getAnnotationByGUID: function(guid) { |
| return this.annotationsByGuid_[guid]; |
| }, |
| |
| addAnnotation: function(annotation) { |
| if (!annotation.guid) |
| throw new Error('Annotation with undefined guid given'); |
| |
| this.annotationsByGuid_[annotation.guid] = annotation; |
| tr.b.dispatchSimpleEvent(this, 'annotationChange'); |
| }, |
| |
| removeAnnotation: function(annotation) { |
| this.annotationsByGuid_[annotation.guid].onRemove(); |
| delete this.annotationsByGuid_[annotation.guid]; |
| tr.b.dispatchSimpleEvent(this, 'annotationChange'); |
| }, |
| |
| getAllAnnotations: function() { |
| return tr.b.dictionaryValues(this.annotationsByGuid_); |
| }, |
| |
| addUserFriendlyCategoryDriver: function(ufcd) { |
| this.userFriendlyCategoryDrivers_.push(ufcd); |
| }, |
| |
| /** |
| * Gets the user friendly category string from an event. |
| * |
| * Returns undefined if none is known. |
| */ |
| getUserFriendlyCategoryFromEvent: function(event) { |
| for (var i = 0; i < this.userFriendlyCategoryDrivers_.length; i++) { |
| var ufc = this.userFriendlyCategoryDrivers_[i].fromEvent(event); |
| if (ufc !== undefined) |
| return ufc; |
| } |
| return undefined; |
| }, |
| |
| /** |
| * @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; |
| }, |
| |
| set importOptions(options) { |
| this.importOptions_ = options; |
| }, |
| |
| /** |
| * Returns a time unit that is used to format values and determines the |
| * precision of the timestamp values. |
| */ |
| get intrinsicTimeUnit() { |
| if (this.intrinsicTimeUnit_ === undefined) |
| return tr.b.u.TimeDisplayModes.ms; |
| return this.intrinsicTimeUnit_; |
| }, |
| |
| set intrinsicTimeUnit(value) { |
| if (this.intrinsicTimeUnit_ === value) |
| return; |
| if (this.intrinsicTimeUnit_ !== undefined) |
| throw new Error('Intrinsic time unit already set'); |
| this.intrinsicTimeUnit_ = value; |
| }, |
| |
| get isTimeHighResolution() { |
| if (this.isTimeHighResolution_ === undefined) |
| this.isTimeHighResolution_ = this.isTimeHighResolutionHeuristic_(); |
| return this.isTimeHighResolution_; |
| }, |
| |
| set isTimeHighResolution(value) { |
| if (this.isTimeHighResolution_ === value) |
| return; |
| if (this.isTimeHighResolution_ !== undefined) |
| throw new Error('isTimeHighResolution already set'); |
| this.isTimeHighResolution_ = value; |
| }, |
| |
| /** |
| * Saves a warning that happened during import. |
| * |
| * Warnings are typically logged to the console, and optionally, the |
| * more critical ones are shown to the user. |
| * |
| * @param {Object} data The import warning data. Data must provide two |
| * accessors: type, message. The types are used to determine if we |
| * should output the message, we'll only output one message of each type. |
| * The message is the actual warning content. |
| */ |
| importWarning: function(data) { |
| data.showToUser = !!data.showToUser; |
| |
| this.importWarnings_.push(data); |
| |
| // Only log each warning type once. We may want to add some kind of |
| // flag to allow reporting all importer warnings. |
| if (this.reportedImportWarnings_[data.type] === true) |
| return; |
| |
| if (this.importOptions_.showImportWarnings) |
| console.warn(data.message); |
| |
| this.reportedImportWarnings_[data.type] = true; |
| }, |
| |
| get hasImportWarnings() { |
| return (this.importWarnings_.length > 0); |
| }, |
| |
| get importWarnings() { |
| return this.importWarnings_; |
| }, |
| |
| get importWarningsThatShouldBeShownToUser() { |
| return this.importWarnings_.filter(function(warning) { |
| return warning.showToUser; |
| }); |
| }, |
| |
| autoCloseOpenSlices: function() { |
| // Sort the samples. |
| this.samples.sort(function(x, y) { |
| return x.start - y.start; |
| }); |
| |
| this.updateBounds(); |
| this.kernel.autoCloseOpenSlices(); |
| for (var pid in this.processes) |
| this.processes[pid].autoCloseOpenSlices(); |
| }, |
| |
| createSubSlices: function() { |
| this.kernel.createSubSlices(); |
| for (var pid in this.processes) |
| this.processes[pid].createSubSlices(); |
| }, |
| |
| preInitializeObjects: function() { |
| for (var pid in this.processes) |
| this.processes[pid].preInitializeObjects(); |
| }, |
| |
| initializeObjects: function() { |
| for (var pid in this.processes) |
| this.processes[pid].initializeObjects(); |
| }, |
| |
| pruneEmptyContainers: function() { |
| this.kernel.pruneEmptyContainers(); |
| for (var pid in this.processes) |
| this.processes[pid].pruneEmptyContainers(); |
| }, |
| |
| mergeKernelWithUserland: function() { |
| for (var pid in this.processes) |
| this.processes[pid].mergeKernelWithUserland(); |
| }, |
| |
| computeWorldBounds: function(shiftWorldToZero) { |
| this.updateBounds(); |
| this.updateCategories_(); |
| |
| if (shiftWorldToZero) |
| this.shiftWorldToZero(); |
| }, |
| |
| buildFlowEventIntervalTree: function() { |
| for (var i = 0; i < this.flowEvents.length; ++i) { |
| var flowEvent = this.flowEvents[i]; |
| this.flowIntervalTree.insert(flowEvent); |
| } |
| this.flowIntervalTree.updateHighValues(); |
| }, |
| |
| cleanupUndeletedObjects: function() { |
| for (var pid in this.processes) |
| this.processes[pid].autoDeleteObjects(this.bounds.max); |
| }, |
| |
| sortMemoryDumps: function() { |
| this.globalMemoryDumps.sort(function(x, y) { |
| return x.start - y.start; |
| }); |
| |
| for (var pid in this.processes) |
| this.processes[pid].sortMemoryDumps(); |
| }, |
| |
| calculateMemoryGraphAttributes: function() { |
| this.globalMemoryDumps.forEach(function(dump) { |
| dump.calculateGraphAttributes(); |
| }); |
| }, |
| |
| buildEventIndices: function() { |
| this.modelIndices = new tr.model.ModelIndices(this); |
| }, |
| |
| sortInteractionRecords: function() { |
| this.interactionRecords.sort(function(x, y) { |
| return x.start - y.start; |
| }); |
| }, |
| |
| sortAlerts: function() { |
| this.alerts.sort(function(x, y) { |
| return x.start - y.start; |
| }); |
| }, |
| |
| isTimeHighResolutionHeuristic_: function() { |
| if (this.intrinsicTimeUnit !== tr.b.u.TimeDisplayModes.ms) |
| return false; |
| // If the timer is only precise to the millisecond, then almost all event |
| // will be precisely X ms apart. We check that by looking at the |
| // decimal part of each event's start time. We create 100 bins for |
| // these fractions. If at least 90% of the events are in the same bin then |
| // the timer is deemed to be low resolution. |
| var nbEvents = 0; |
| var nbPerBin = []; |
| var maxEvents = 0; |
| for (var i = 0; i < 100; ++i) |
| nbPerBin.push(0); |
| this.iterateAllEvents(function(event) { |
| nbEvents++; |
| if (event.start !== undefined) { |
| // Compute |
| var remainder = Math.floor( |
| (event.start - Math.floor(event.start)) * 100); |
| nbPerBin[remainder]++; |
| maxEvents = Math.max(maxEvents, nbPerBin[remainder]); |
| } |
| }); |
| // If there are too few events our heuristic is not very good, assume the |
| // timer is high resolution. |
| if (nbEvents < 100) |
| return true; |
| // If more than 90% of the events are snapped precisely on milliseconds |
| // boundary we got a trace with a low resolution timer. |
| return (maxEvents / nbEvents) < 0.9; |
| } |
| }; |
| |
| return { |
| ClockSyncRecord: ClockSyncRecord, |
| Model: Model |
| }; |
| }); |
| </script> |