model.html revision 46b43bff003ceda46cf9a5d40a47f7674996d2e0
1<!DOCTYPE html>
2<!--
3Copyright (c) 2012 The Chromium Authors. All rights reserved.
4Use of this source code is governed by a BSD-style license that can be
5found in the LICENSE file.
6-->
7
8<link rel="import" href="/tracing/base/base.html">
9<link rel="import" href="/tracing/base/event.html">
10<link rel="import" href="/tracing/base/interval_tree.html">
11<link rel="import" href="/tracing/base/range.html">
12<link rel="import" href="/tracing/base/task.html">
13<link rel="import" href="/tracing/base/units/units.html">
14<link rel="import" href="/tracing/core/auditor.html">
15<link rel="import" href="/tracing/core/filter.html">
16<link rel="import" href="/tracing/model/alert.html">
17<link rel="import" href="/tracing/model/device.html">
18<link rel="import" href="/tracing/model/flow_event.html">
19<link rel="import" href="/tracing/model/frame.html">
20<link rel="import" href="/tracing/model/global_memory_dump.html">
21<link rel="import" href="/tracing/model/instant_event.html">
22<link rel="import" href="/tracing/model/interaction_record.html">
23<link rel="import" href="/tracing/model/kernel.html">
24<link rel="import" href="/tracing/model/model_indices.html">
25<link rel="import" href="/tracing/model/process.html">
26<link rel="import" href="/tracing/model/process_memory_dump.html">
27<link rel="import" href="/tracing/model/sample.html">
28<link rel="import" href="/tracing/model/stack_frame.html">
29<link rel="import" href="/tracing/ui/base/overlay.html">
30
31<script>
32'use strict';
33
34/**
35 * @fileoverview Model is a parsed representation of the
36 * TraceEvents obtained from base/trace_event in which the begin-end
37 * tokens are converted into a hierarchy of processes, threads,
38 * subrows, and slices.
39 *
40 * The building block of the model is a slice. A slice is roughly
41 * equivalent to function call executing on a specific thread. As a
42 * result, slices may have one or more subslices.
43 *
44 * A thread contains one or more subrows of slices. Row 0 corresponds to
45 * the "root" slices, e.g. the topmost slices. Row 1 contains slices that
46 * are nested 1 deep in the stack, and so on. We use these subrows to draw
47 * nesting tasks.
48 *
49 */
50tr.exportTo('tr', function() {
51  var Process = tr.model.Process;
52  var Device = tr.model.Device;
53  var Kernel = tr.model.Kernel;
54  var GlobalMemoryDump = tr.model.GlobalMemoryDump;
55  var GlobalInstantEvent = tr.model.GlobalInstantEvent;
56  var FlowEvent = tr.model.FlowEvent;
57  var Alert = tr.model.Alert;
58  var InteractionRecord = tr.model.InteractionRecord;
59  var Sample = tr.model.Sample;
60
61  function ClockSyncRecord(name, ts, args) {
62    this.name = name;
63    this.ts = ts;
64    this.args = args;
65  }
66
67  /**
68   * @constructor
69   */
70  function Model() {
71    tr.model.EventContainer.call(this);
72    tr.b.EventTarget.decorate(this);
73
74    this.timestampShiftToZeroAmount_ = 0;
75
76    this.faviconHue = 'blue'; // Should be a key from favicons.html
77
78    this.device = new Device(this);
79    this.kernel = new Kernel(this);
80    this.processes = {};
81    this.metadata = [];
82    this.categories = [];
83    this.instantEvents = [];
84    this.flowEvents = [];
85    this.clockSyncRecords = [];
86    this.intrinsicTimeUnit_ = undefined;
87
88    this.stackFrames = {};
89    this.samples = [];
90
91    this.alerts = [];
92    this.interactionRecords = [];
93
94    this.flowIntervalTree = new tr.b.IntervalTree(
95        function(f) { return f.start; },
96        function(f) { return f.end; });
97
98    this.globalMemoryDumps = [];
99
100    this.annotationsByGuid_ = {};
101    this.modelIndices = undefined;
102
103    this.importWarnings_ = [];
104    this.reportedImportWarnings_ = {};
105  }
106
107  Model.prototype = {
108    __proto__: tr.model.EventContainer.prototype,
109
110    iterateAllEventsInThisContainer: function(eventTypePredicate,
111                                              callback, opt_this) {
112      if (eventTypePredicate.call(opt_this, GlobalMemoryDump))
113        this.globalMemoryDumps.forEach(callback, opt_this);
114
115      if (eventTypePredicate.call(opt_this, GlobalInstantEvent))
116        this.instantEvents.forEach(callback, opt_this);
117
118      if (eventTypePredicate.call(opt_this, FlowEvent))
119        this.flowEvents.forEach(callback, opt_this);
120
121      if (eventTypePredicate.call(opt_this, Alert))
122        this.alerts.forEach(callback, opt_this);
123
124      if (eventTypePredicate.call(opt_this, InteractionRecord))
125        this.interactionRecords.forEach(callback, opt_this);
126
127      if (eventTypePredicate.call(opt_this, Sample))
128        this.samples.forEach(callback, opt_this);
129    },
130
131    iterateAllChildEventContainers: function(callback, opt_this) {
132      callback.call(opt_this, this.device);
133      callback.call(opt_this, this.kernel);
134      for (var pid in this.processes)
135        callback.call(opt_this, this.processes[pid]);
136    },
137
138    /**
139     * Some objects in the model can persist their state in ModelSettings.
140     *
141     * This iterates through them.
142     */
143    iterateAllPersistableObjects: function(callback) {
144      this.kernel.iterateAllPersistableObjects(callback);
145      for (var pid in this.processes)
146        this.processes[pid].iterateAllPersistableObjects(callback);
147    },
148
149    updateBounds: function() {
150      this.bounds.reset();
151      var bounds = this.bounds;
152
153      this.iterateAllChildEventContainers(function(ec) {
154        ec.updateBounds();
155        bounds.addRange(ec.bounds);
156      });
157      this.iterateAllEventsInThisContainer(
158          function(eventConstructor) { return true; },
159          function(event) {
160            event.addBoundsToRange(bounds);
161          });
162    },
163
164    shiftWorldToZero: function() {
165      var shiftAmount = -this.bounds.min;
166      this.timestampShiftToZeroAmount_ = shiftAmount;
167      this.iterateAllChildEventContainers(function(ec) {
168        ec.shiftTimestampsForward(shiftAmount);
169      });
170      this.iterateAllEventsInThisContainer(
171        function(eventConstructor) { return true; },
172        function(event) {
173          event.start += shiftAmount;
174        });
175      this.updateBounds();
176    },
177
178    convertTimestampToModelTime: function(sourceClockDomainName, ts) {
179      if (sourceClockDomainName !== 'traceEventClock')
180        throw new Error('Only traceEventClock is supported.');
181      return tr.b.u.Units.timestampFromUs(ts) +
182        this.timestampShiftToZeroAmount_;
183    },
184
185    get numProcesses() {
186      var n = 0;
187      for (var p in this.processes)
188        n++;
189      return n;
190    },
191
192    /**
193     * @return {Process} Gets a TimelineProcess for a specified pid. Returns
194     * undefined if the process doesn't exist.
195     */
196    getProcess: function(pid) {
197      return this.processes[pid];
198    },
199
200    /**
201     * @return {Process} Gets a TimelineProcess for a specified pid or
202     * creates one if it does not exist.
203     */
204    getOrCreateProcess: function(pid) {
205      if (!this.processes[pid])
206        this.processes[pid] = new Process(this, pid);
207      return this.processes[pid];
208    },
209
210    pushInstantEvent: function(instantEvent) {
211      this.instantEvents.push(instantEvent);
212    },
213
214    addStackFrame: function(stackFrame) {
215      if (this.stackFrames[stackFrame.id])
216        throw new Error('Stack frame already exists');
217      this.stackFrames[stackFrame.id] = stackFrame;
218      return stackFrame;
219    },
220
221    addInteractionRecord: function(ir) {
222      this.interactionRecords.push(ir);
223      return ir;
224    },
225
226    getClockSyncRecordsNamed: function(name) {
227      return this.clockSyncRecords.filter(function(x) {
228        return x.name === name;
229      });
230    },
231
232    /**
233     * Generates the set of categories from the slices and counters.
234     */
235    updateCategories_: function() {
236      var categoriesDict = {};
237      this.device.addCategoriesToDict(categoriesDict);
238      this.kernel.addCategoriesToDict(categoriesDict);
239      for (var pid in this.processes)
240        this.processes[pid].addCategoriesToDict(categoriesDict);
241
242      this.categories = [];
243      for (var category in categoriesDict)
244        if (category != '')
245          this.categories.push(category);
246    },
247
248    getAllThreads: function() {
249      var threads = [];
250      for (var tid in this.kernel.threads) {
251        threads.push(process.threads[tid]);
252      }
253      for (var pid in this.processes) {
254        var process = this.processes[pid];
255        for (var tid in process.threads) {
256          threads.push(process.threads[tid]);
257        }
258      }
259      return threads;
260    },
261
262    /**
263     * @return {Array} An array of all processes in the model.
264     */
265    getAllProcesses: function() {
266      var processes = [];
267      for (var pid in this.processes)
268        processes.push(this.processes[pid]);
269      return processes;
270    },
271
272    /**
273     * @return {Array} An array of all the counters in the model.
274     */
275    getAllCounters: function() {
276      var counters = [];
277      counters.push.apply(
278          counters, tr.b.dictionaryValues(this.device.counters));
279      counters.push.apply(
280          counters, tr.b.dictionaryValues(this.kernel.counters));
281      for (var pid in this.processes) {
282        var process = this.processes[pid];
283        for (var tid in process.counters) {
284          counters.push(process.counters[tid]);
285        }
286      }
287      return counters;
288    },
289
290    getAnnotationByGUID: function(guid) {
291      return this.annotationsByGuid_[guid];
292    },
293
294    addAnnotation: function(annotation) {
295      if (!annotation.guid)
296        throw new Error('Annotation with undefined guid given');
297
298      this.annotationsByGuid_[annotation.guid] = annotation;
299      tr.b.dispatchSimpleEvent(this, 'annotationChange');
300    },
301
302    removeAnnotation: function(annotation) {
303      this.annotationsByGuid_[annotation.guid].onRemove();
304      delete this.annotationsByGuid_[annotation.guid];
305      tr.b.dispatchSimpleEvent(this, 'annotationChange');
306    },
307
308    getAllAnnotations: function() {
309      return tr.b.dictionaryValues(this.annotationsByGuid_);
310    },
311
312    /**
313     * @param {String} The name of the thread to find.
314     * @return {Array} An array of all the matched threads.
315     */
316    findAllThreadsNamed: function(name) {
317      var namedThreads = [];
318      namedThreads.push.apply(
319          namedThreads,
320          this.kernel.findAllThreadsNamed(name));
321      for (var pid in this.processes) {
322        namedThreads.push.apply(
323            namedThreads,
324            this.processes[pid].findAllThreadsNamed(name));
325      }
326      return namedThreads;
327    },
328
329    set importOptions(options) {
330      this.importOptions_ = options;
331    },
332
333    /**
334     * Returns a time unit that is used to format values and determines the
335     * precision of the timestamp values.
336     */
337    get intrinsicTimeUnit() {
338      if (this.intrinsicTimeUnit_ === undefined)
339        return tr.b.u.TimeDisplayModes.ms;
340      return this.intrinsicTimeUnit_;
341    },
342
343    set intrinsicTimeUnit(value) {
344      if (this.intrinsicTimeUnit_ === value)
345        return;
346      if (this.intrinsicTimeUnit_ !== undefined)
347        throw new Error('Intrinsic time unit already set');
348      this.intrinsicTimeUnit_ = value;
349    },
350
351    /**
352     * @param {Object} data The import warning data. Data must provide two
353     *    accessors: type, message. The types are used to determine if we
354     *    should output the message, we'll only output one message of each type.
355     *    The message is the actual warning content.
356     */
357    importWarning: function(data) {
358      this.importWarnings_.push(data);
359
360      // Only log each warning type once. We may want to add some kind of
361      // flag to allow reporting all importer warnings.
362      if (this.reportedImportWarnings_[data.type] === true)
363        return;
364
365      if (this.importOptions_.showImportWarnings)
366        console.warn(data.message);
367
368      this.reportedImportWarnings_[data.type] = true;
369    },
370
371    get hasImportWarnings() {
372      return (this.importWarnings_.length > 0);
373    },
374
375    get importWarnings() {
376      return this.importWarnings_;
377    },
378
379    autoCloseOpenSlices: function() {
380      // Sort the samples.
381      this.samples.sort(function(x, y) {
382        return x.start - y.start;
383      });
384
385      this.updateBounds();
386      this.kernel.autoCloseOpenSlices(this.bounds.max);
387      for (var pid in this.processes)
388        this.processes[pid].autoCloseOpenSlices(this.bounds.max);
389    },
390
391    createSubSlices: function() {
392      this.kernel.createSubSlices();
393      for (var pid in this.processes)
394        this.processes[pid].createSubSlices();
395    },
396
397    preInitializeObjects: function() {
398      for (var pid in this.processes)
399        this.processes[pid].preInitializeObjects();
400    },
401
402    initializeObjects: function() {
403      for (var pid in this.processes)
404        this.processes[pid].initializeObjects();
405    },
406
407    pruneEmptyContainers: function() {
408      this.kernel.pruneEmptyContainers();
409      for (var pid in this.processes)
410        this.processes[pid].pruneEmptyContainers();
411    },
412
413    mergeKernelWithUserland: function() {
414      for (var pid in this.processes)
415        this.processes[pid].mergeKernelWithUserland();
416    },
417
418    computeWorldBounds: function(shiftWorldToZero) {
419      this.updateBounds();
420      this.updateCategories_();
421
422      if (shiftWorldToZero)
423        this.shiftWorldToZero();
424    },
425
426    buildFlowEventIntervalTree: function() {
427      for (var i = 0; i < this.flowEvents.length; ++i) {
428        var flowEvent = this.flowEvents[i];
429        this.flowIntervalTree.insert(flowEvent);
430      }
431      this.flowIntervalTree.updateHighValues();
432    },
433
434    cleanupUndeletedObjects: function() {
435      for (var pid in this.processes)
436        this.processes[pid].autoDeleteObjects(this.bounds.max);
437    },
438
439    sortMemoryDumps: function() {
440      this.globalMemoryDumps.sort(function(x, y) {
441        return x.start - y.start;
442      });
443
444      for (var pid in this.processes)
445        this.processes[pid].sortMemoryDumps();
446    },
447
448    calculateMemoryGraphAttributes: function() {
449      this.globalMemoryDumps.forEach(function(dump) {
450        dump.calculateGraphAttributes();
451      });
452    },
453
454    buildEventIndices: function() {
455      this.modelIndices = new tr.model.ModelIndices(this);
456    },
457
458    sortInteractionRecords: function() {
459      this.interactionRecords.sort(function(x, y) {
460        return x.start - y.start;
461      });
462    },
463
464    sortAlerts: function() {
465      this.alerts.sort(function(x, y) {
466        return x.start - y.start;
467      });
468    }
469  };
470
471  return {
472    ClockSyncRecord: ClockSyncRecord,
473    Model: Model
474  };
475});
476</script>
477