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