• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 Provides the Thread class.
9 */
10base.require('base.guid');
11base.require('base.range');
12base.require('tracing.trace_model.slice');
13base.require('tracing.trace_model.slice_group');
14base.require('tracing.trace_model.async_slice_group');
15base.require('tracing.trace_model.sample');
16
17base.exportTo('tracing.trace_model', function() {
18
19  var Slice = tracing.trace_model.Slice;
20  var SliceGroup = tracing.trace_model.SliceGroup;
21  var AsyncSlice = tracing.trace_model.AsyncSlice;
22  var AsyncSliceGroup = tracing.trace_model.AsyncSliceGroup;
23
24  /**
25   * A ThreadSlice represents an interval of time on a thread resource
26   * with associated nestinged slice information.
27   *
28   * ThreadSlices are typically associated with a specific trace event pair on a
29   * specific thread.
30   * For example,
31   *   TRACE_EVENT_BEGIN1("x","myArg", 7) at time=0.1ms
32   *   TRACE_EVENT_END0()                 at time=0.3ms
33   * This results in a single slice from 0.1 with duration 0.2 on a
34   * specific thread.
35   *
36   * @constructor
37   */
38  function ThreadSlice(cat, title, colorId, start, args, opt_duration) {
39    Slice.call(this, cat, title, colorId, start, args, opt_duration);
40    // Do not modify this directly.
41    // subSlices is configured by SliceGroup.rebuildSubRows_.
42    this.subSlices = [];
43  }
44
45  ThreadSlice.prototype = {
46    __proto__: Slice.prototype
47  };
48
49  /**
50   * A Thread stores all the trace events collected for a particular
51   * thread. We organize the synchronous slices on a thread by "subrows," where
52   * subrow 0 has all the root slices, subrow 1 those nested 1 deep, and so on.
53   * The asynchronous slices are stored in an AsyncSliceGroup object.
54   *
55   * The slices stored on a Thread should be instances of
56   * ThreadSlice.
57   *
58   * @constructor
59   */
60  function Thread(parent, tid) {
61    this.guid_ = base.GUID.allocate();
62    if (!parent)
63      throw new Error('Parent must be provided.');
64    this.parent = parent;
65    this.sortIndex = 0;
66    this.tid = tid;
67    this.sliceGroup = new SliceGroup(ThreadSlice);
68    this.cpuSlices = undefined;
69    this.samples_ = [];
70    this.kernelSliceGroup = new SliceGroup();
71    this.asyncSliceGroup = new AsyncSliceGroup();
72    this.bounds = new base.Range();
73    this.ephemeralSettings = {};
74  }
75
76  Thread.prototype = {
77
78    /*
79     * @return {Number} A globally unique identifier for this counter.
80     */
81    get guid() {
82      return this.guid_;
83    },
84
85    compareTo: function(that) {
86      return Thread.compare(this, that);
87    },
88
89    toJSON: function() {
90      var obj = new Object();
91      var keys = Object.keys(this);
92      for (var i = 0; i < keys.length; i++) {
93        var key = keys[i];
94        if (typeof this[key] == 'function')
95          continue;
96        if (key == 'parent') {
97          obj[key] = this[key].guid;
98          continue;
99        }
100        obj[key] = this[key];
101      }
102      return obj;
103    },
104
105    /**
106     * Adds a new sample in the thread's samples.
107     *
108     * Calls to addSample must be made with non-monotonically-decreasing
109     * timestamps.
110     *
111     * @param {String} category Category of the sample to add.
112     * @param {String} title Title of the sample to add.
113     * @param {Number} ts The timetsamp of the sample, in milliseconds.
114     * @param {Object.<string, Object>=} opt_args Arguments associated with
115     * the sample.
116     */
117    addSample: function(category, title, ts, opt_args) {
118      if (this.samples_.length) {
119        var lastSample = this.samples_[this.samples_.length - 1];
120        if (ts < lastSample.start) {
121          throw new
122              Error('Samples must be added in increasing timestamp order.');
123        }
124      }
125      var colorId = tracing.getStringColorId(title);
126      var sample = new tracing.trace_model.Sample(category, title, colorId, ts,
127                                                  opt_args ? opt_args : {});
128      this.samples_.push(sample);
129      return sample;
130    },
131
132    /**
133     * Returns the array of samples added to this thread. If no samples
134     * have been added, an empty array is returned.
135     *
136     * @return {Array<Sample>} array of samples.
137     */
138    get samples() {
139      return this.samples_;
140    },
141
142    /**
143     * Name of the thread, if present.
144     */
145    name: undefined,
146
147    /**
148     * Shifts all the timestamps inside this thread forward by the amount
149     * specified.
150     */
151    shiftTimestampsForward: function(amount) {
152      this.sliceGroup.shiftTimestampsForward(amount);
153
154      if (this.cpuSlices) {
155        for (var i = 0; i < this.cpuSlices.length; i++) {
156          var slice = this.cpuSlices[i];
157          slice.start += amount;
158        }
159      }
160
161      if (this.samples_.length) {
162        for (var i = 0; i < this.samples_.length; i++) {
163          var sample = this.samples_[i];
164          sample.start += amount;
165        }
166      }
167
168      this.kernelSliceGroup.shiftTimestampsForward(amount);
169      this.asyncSliceGroup.shiftTimestampsForward(amount);
170    },
171
172    /**
173     * Determins whether this thread is empty. If true, it usually implies
174     * that it should be pruned from the model.
175     */
176    get isEmpty() {
177      if (this.sliceGroup.length)
178        return false;
179      if (this.sliceGroup.openSliceCount)
180        return false;
181      if (this.cpuSlices && this.cpuSlices.length)
182        return false;
183      if (this.kernelSliceGroup.length)
184        return false;
185      if (this.asyncSliceGroup.length)
186        return false;
187      if (this.samples_.length)
188        return false;
189      return true;
190    },
191
192    /**
193     * Updates the bounds based on the
194     * current objects associated with the thread.
195     */
196    updateBounds: function() {
197      this.bounds.reset();
198
199      this.sliceGroup.updateBounds();
200      this.bounds.addRange(this.sliceGroup.bounds);
201
202      this.kernelSliceGroup.updateBounds();
203      this.bounds.addRange(this.kernelSliceGroup.bounds);
204
205      this.asyncSliceGroup.updateBounds();
206      this.bounds.addRange(this.asyncSliceGroup.bounds);
207
208      if (this.cpuSlices && this.cpuSlices.length) {
209        this.bounds.addValue(this.cpuSlices[0].start);
210        this.bounds.addValue(
211            this.cpuSlices[this.cpuSlices.length - 1].end);
212      }
213      if (this.samples_.length) {
214        this.bounds.addValue(this.samples_[0].start);
215        this.bounds.addValue(
216            this.samples_[this.samples_.length - 1].end);
217      }
218    },
219
220    addCategoriesToDict: function(categoriesDict) {
221      for (var i = 0; i < this.sliceGroup.length; i++)
222        categoriesDict[this.sliceGroup.slices[i].category] = true;
223      for (var i = 0; i < this.kernelSliceGroup.length; i++)
224        categoriesDict[this.kernelSliceGroup.slices[i].category] = true;
225      for (var i = 0; i < this.asyncSliceGroup.length; i++)
226        categoriesDict[this.asyncSliceGroup.slices[i].category] = true;
227      for (var i = 0; i < this.samples_.length; i++)
228        categoriesDict[this.samples_[i].category] = true;
229    },
230
231    autoCloseOpenSlices: function(opt_maxTimestamp) {
232      this.sliceGroup.autoCloseOpenSlices(opt_maxTimestamp);
233      this.kernelSliceGroup.autoCloseOpenSlices(opt_maxTimestamp);
234    },
235
236    mergeKernelWithUserland: function() {
237      if (this.kernelSliceGroup.length > 0) {
238        var newSlices = SliceGroup.merge(
239            this.sliceGroup, this.kernelSliceGroup);
240        this.sliceGroup.slices = newSlices.slices;
241        this.kernelSliceGroup = new SliceGroup();
242        this.updateBounds();
243      }
244    },
245
246    /**
247     * @return {String} A user-friendly name for this thread.
248     */
249    get userFriendlyName() {
250      return this.name || this.tid;
251    },
252
253    /**
254     * @return {String} User friendly details about this thread.
255     */
256    get userFriendlyDetails() {
257      return 'tid: ' + this.tid +
258          (this.name ? ', name: ' + this.name : '');
259    },
260
261    getSettingsKey: function() {
262      if (!this.name)
263        return undefined;
264      var parentKey = this.parent.getSettingsKey();
265      if (!parentKey)
266        return undefined;
267      return parentKey + '.' + this.name;
268    }
269  };
270
271  /**
272   * Comparison between threads that orders first by parent.compareTo,
273   * then by names, then by tid.
274   */
275  Thread.compare = function(x, y) {
276    var tmp = x.parent.compareTo(y.parent);
277    if (tmp)
278      return tmp;
279
280    tmp = x.sortIndex - y.sortIndex;
281    if (tmp)
282      return tmp;
283
284    tmp = base.comparePossiblyUndefinedValues(
285        x.name, y.name,
286        function(x, y) { return x.localeCompare(y); });
287    if (tmp)
288      return tmp;
289
290    return x.tid - y.tid;
291  };
292
293  return {
294    ThreadSlice: ThreadSlice,
295    Thread: Thread
296  };
297});
298