1<!DOCTYPE html>
2<!--
3Copyright (c) 2013 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/model/event_container.html">
9<link rel="import" href="/tracing/model/async_slice_group.html">
10<link rel="import" href="/tracing/model/slice_group.html">
11<link rel="import" href="/tracing/model/thread_slice.html">
12<link rel="import" href="/tracing/base/guid.html">
13<link rel="import" href="/tracing/base/range.html">
14
15<script>
16'use strict';
17
18/**
19 * @fileoverview Provides the Thread class.
20 */
21tr.exportTo('tr.model', function() {
22  var AsyncSlice = tr.model.AsyncSlice;
23  var AsyncSliceGroup = tr.model.AsyncSliceGroup;
24  var Slice = tr.model.Slice;
25  var SliceGroup = tr.model.SliceGroup;
26  var ThreadSlice = tr.model.ThreadSlice;
27  var ThreadTimeSlice = tr.model.ThreadTimeSlice;
28
29  /**
30   * A Thread stores all the trace events collected for a particular
31   * thread. We organize the synchronous slices on a thread by "subrows," where
32   * subrow 0 has all the root slices, subrow 1 those nested 1 deep, and so on.
33   * The asynchronous slices are stored in an AsyncSliceGroup object.
34   *
35   * The slices stored on a Thread should be instances of
36   * ThreadSlice.
37   *
38   * @constructor
39   * @extends {tr.model.EventContainer}
40   */
41  function Thread(parent, tid) {
42    if (!parent)
43      throw new Error('Parent must be provided.');
44
45    tr.model.EventContainer.call(this);
46    this.parent = parent;
47    this.sortIndex = 0;
48    this.tid = tid;
49    this.name = undefined;
50    this.samples_ = undefined; // Set during createSubSlices
51
52    var that = this;
53
54    this.sliceGroup = new SliceGroup(this, ThreadSlice, 'slices');
55    this.timeSlices = undefined;
56    this.kernelSliceGroup = new SliceGroup(
57        this, ThreadSlice, 'kernel-slices');
58    this.asyncSliceGroup = new AsyncSliceGroup(this, 'async-slices');
59  }
60
61  Thread.prototype = {
62    __proto__: tr.model.EventContainer.prototype,
63
64    get model() {
65      return this.parent.model;
66    },
67
68    get stableId() {
69      return this.parent.stableId + '.' + this.tid;
70    },
71
72    compareTo: function(that) {
73      return Thread.compare(this, that);
74    },
75
76    iterateAllChildEventContainers: function(callback, opt_this) {
77      if (this.sliceGroup.length)
78        callback.call(opt_this, this.sliceGroup);
79      if (this.kernelSliceGroup.length)
80        callback.call(opt_this, this.kernelSliceGroup);
81      if (this.asyncSliceGroup.length)
82        callback.call(opt_this, this.asyncSliceGroup);
83    },
84
85    iterateAllEventsInThisContainer: function(eventTypePredicate,
86                                              callback, opt_this) {
87      if (this.timeSlices && this.timeSlices.length) {
88        if (eventTypePredicate.call(opt_this, ThreadTimeSlice))
89          this.timeSlices.forEach(callback, opt_this);
90      }
91    },
92
93    iterateAllPersistableObjects: function(cb) {
94      cb(this);
95      if (this.sliceGroup.length)
96        cb(this.sliceGroup);
97      this.asyncSliceGroup.viewSubGroups.forEach(cb);
98    },
99
100    /**
101     * Shifts all the timestamps inside this thread forward by the amount
102     * specified.
103     */
104    shiftTimestampsForward: function(amount) {
105      this.sliceGroup.shiftTimestampsForward(amount);
106
107      if (this.timeSlices) {
108        for (var i = 0; i < this.timeSlices.length; i++) {
109          var slice = this.timeSlices[i];
110          slice.start += amount;
111        }
112      }
113
114      this.kernelSliceGroup.shiftTimestampsForward(amount);
115      this.asyncSliceGroup.shiftTimestampsForward(amount);
116    },
117
118    /**
119     * Determines whether this thread is empty. If true, it usually implies
120     * that it should be pruned from the model.
121     */
122    get isEmpty() {
123      if (this.sliceGroup.length)
124        return false;
125      if (this.sliceGroup.openSliceCount)
126        return false;
127      if (this.timeSlices && this.timeSlices.length)
128        return false;
129      if (this.kernelSliceGroup.length)
130        return false;
131      if (this.asyncSliceGroup.length)
132        return false;
133      if (this.samples_.length)
134        return false;
135      return true;
136    },
137
138    /**
139     * Updates the bounds based on the
140     * current objects associated with the thread.
141     */
142    updateBounds: function() {
143      this.bounds.reset();
144
145      this.sliceGroup.updateBounds();
146      this.bounds.addRange(this.sliceGroup.bounds);
147
148      this.kernelSliceGroup.updateBounds();
149      this.bounds.addRange(this.kernelSliceGroup.bounds);
150
151      this.asyncSliceGroup.updateBounds();
152      this.bounds.addRange(this.asyncSliceGroup.bounds);
153
154      if (this.timeSlices && this.timeSlices.length) {
155        this.bounds.addValue(this.timeSlices[0].start);
156        this.bounds.addValue(
157            this.timeSlices[this.timeSlices.length - 1].end);
158      }
159
160      if (this.samples_ && this.samples_.length) {
161        this.bounds.addValue(this.samples_[0].start);
162        this.bounds.addValue(
163            this.samples_[this.samples_.length - 1].end);
164      }
165    },
166
167    addCategoriesToDict: function(categoriesDict) {
168      for (var i = 0; i < this.sliceGroup.length; i++)
169        categoriesDict[this.sliceGroup.slices[i].category] = true;
170      for (var i = 0; i < this.kernelSliceGroup.length; i++)
171        categoriesDict[this.kernelSliceGroup.slices[i].category] = true;
172      for (var i = 0; i < this.asyncSliceGroup.length; i++)
173        categoriesDict[this.asyncSliceGroup.slices[i].category] = true;
174      if (this.samples_) {
175        for (var i = 0; i < this.samples_.length; i++)
176          categoriesDict[this.samples_[i].category] = true;
177      }
178    },
179
180    autoCloseOpenSlices: function() {
181      this.sliceGroup.autoCloseOpenSlices();
182      this.kernelSliceGroup.autoCloseOpenSlices();
183    },
184
185    mergeKernelWithUserland: function() {
186      if (this.kernelSliceGroup.length > 0) {
187        var newSlices = SliceGroup.merge(
188            this.sliceGroup, this.kernelSliceGroup);
189        this.sliceGroup.slices = newSlices.slices;
190        this.kernelSliceGroup = new SliceGroup(this);
191        this.updateBounds();
192      }
193    },
194
195    createSubSlices: function() {
196      this.sliceGroup.createSubSlices();
197      this.samples_ = this.parent.model.samples.filter(function(sample) {
198        return sample.thread == this;
199      }, this);
200    },
201
202    /**
203     * @return {String} A user-friendly name for this thread.
204     */
205    get userFriendlyName() {
206      return this.name || this.tid;
207    },
208
209    /**
210     * @return {String} User friendly details about this thread.
211     */
212    get userFriendlyDetails() {
213      return 'tid: ' + this.tid +
214          (this.name ? ', name: ' + this.name : '');
215    },
216
217    getSettingsKey: function() {
218      if (!this.name)
219        return undefined;
220      var parentKey = this.parent.getSettingsKey();
221      if (!parentKey)
222        return undefined;
223      return parentKey + '.' + this.name;
224    },
225
226    getProcess: function() {
227      return this.parent;
228    },
229
230    /*
231     * Returns the index of the slice in the timeSlices array, or undefined.
232     */
233    indexOfTimeSlice: function(timeSlice) {
234      var i = tr.b.findLowIndexInSortedArray(
235          this.timeSlices,
236          function(slice) { return slice.start; },
237          timeSlice.start);
238      if (this.timeSlices[i] !== timeSlice)
239        return undefined;
240      return i;
241    },
242
243    /*
244     * Returns an object with the CPU number used as keys,
245     * and the value of each key object is the amount of milliseconds spent
246     * running on this CPU.
247     * Additionally, stats.total contains the total time
248     * spent running on all CPUs.
249     */
250    getCpuStatsForRange: function(range) {
251      var stats = {};
252      stats.total = 0;
253
254      if (!this.timeSlices)
255        return stats;
256
257      function addStatsForSlice(threadTimeSlice) {
258        var freqRange = tr.b.Range.fromExplicitRange(threadTimeSlice.start,
259            threadTimeSlice.end);
260        var intersection = freqRange.findIntersection(range);
261
262        if (threadTimeSlice.schedulingState ==
263            tr.model.SCHEDULING_STATE.RUNNING) {
264          var cpu = threadTimeSlice.cpuOnWhichThreadWasRunning;
265          if (!(cpu.cpuNumber in stats))
266            stats[cpu.cpuNumber] = 0;
267
268          stats[cpu.cpuNumber] += intersection.duration;
269          stats.total += intersection.duration;
270        }
271      }
272
273      tr.b.iterateOverIntersectingIntervals(this.timeSlices,
274                                            function(x) { return x.start; },
275                                            function(x) { return x.end; },
276                                            range.min,
277                                            range.max,
278                                            addStatsForSlice);
279      return stats;
280    },
281
282    getSchedulingStatsForRange: function(start, end) {
283      var stats = {};
284
285      if (!this.timeSlices) return stats;
286
287      function addStatsForSlice(threadTimeSlice) {
288        var overlapStart = Math.max(threadTimeSlice.start, start);
289        var overlapEnd = Math.min(threadTimeSlice.end, end);
290        var schedulingState = threadTimeSlice.schedulingState;
291
292        if (!(schedulingState in stats))
293          stats[schedulingState] = 0;
294        stats[schedulingState] += overlapEnd - overlapStart;
295      }
296
297      tr.b.iterateOverIntersectingIntervals(this.timeSlices,
298                                            function(x) { return x.start; },
299                                            function(x) { return x.end; },
300                                            start,
301                                            end,
302                                            addStatsForSlice);
303      return stats;
304    },
305
306    get samples() {
307      return this.samples_;
308    }
309  };
310
311  /**
312   * Comparison between threads that orders first by parent.compareTo,
313   * then by names, then by tid.
314   */
315  Thread.compare = function(x, y) {
316    var tmp = x.parent.compareTo(y.parent);
317    if (tmp)
318      return tmp;
319
320    tmp = x.sortIndex - y.sortIndex;
321    if (tmp)
322      return tmp;
323
324    tmp = tr.b.comparePossiblyUndefinedValues(
325        x.name, y.name,
326        function(x, y) { return x.localeCompare(y); });
327    if (tmp)
328      return tmp;
329
330    return x.tid - y.tid;
331  };
332
333  return {
334    Thread: Thread
335  };
336});
337</script>
338