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/base/range.html">
9<link rel="import" href="/tracing/model/counter.html">
10<link rel="import" href="/tracing/model/cpu_slice.html">
11<link rel="import" href="/tracing/model/thread_time_slice.html">
12
13<script>
14'use strict';
15
16/**
17 * @fileoverview Provides the Cpu class.
18 */
19tr.exportTo('tr.model', function() {
20
21  var ColorScheme = tr.b.ColorScheme;
22  var Counter = tr.model.Counter;
23  var CpuSlice = tr.model.CpuSlice;
24  var Slice = tr.model.Slice;
25
26  /**
27   * The Cpu represents a Cpu from the kernel's point of view.
28   * @constructor
29   */
30  function Cpu(kernel, number) {
31    if (kernel === undefined || number === undefined)
32      throw new Error('Missing arguments');
33    this.kernel = kernel;
34    this.cpuNumber = number;
35    this.slices = [];
36    this.counters = {};
37    this.bounds = new tr.b.Range();
38    this.samples_ = undefined; // Set during createSubSlices
39
40    // Start timestamp of the last active thread.
41    this.lastActiveTimestamp_ = undefined;
42
43    // Identifier of the last active thread. On Linux, it's a pid while on
44    // Windows it's a thread id.
45    this.lastActiveThread_ = undefined;
46
47    // Name and arguments of the last active thread.
48    this.lastActiveName_ = undefined;
49    this.lastActiveArgs_ = undefined;
50  };
51
52  Cpu.prototype = {
53    get samples() {
54      return this.samples_;
55    },
56
57    get userFriendlyName() {
58      return 'CPU ' + this.cpuNumber;
59    },
60
61    iterateAllEventsInThisContainer: function(eventTypePredicate,
62                                              callback, opt_this) {
63      if (eventTypePredicate.call(opt_this, tr.model.CpuSlice))
64        this.slices.forEach(callback, opt_this);
65
66      if (this.samples_) {
67        if (eventTypePredicate.call(opt_this, tr.model.Sample))
68          this.samples_.forEach(callback, opt_this);
69      }
70    },
71
72    iterateAllChildEventContainers: function(callback, opt_this) {
73      for (var id in this.counters)
74        callback.call(opt_this, this.counters[id]);
75    },
76
77    /**
78     * @return {Counter} The counter on this CPU with the given category/name
79     * combination, creating it if it doesn't exist.
80     */
81    getOrCreateCounter: function(cat, name) {
82      var id = cat + '.' + name;
83      if (!this.counters[id])
84        this.counters[id] = new Counter(this, id, cat, name);
85      return this.counters[id];
86    },
87
88    /**
89     * @return {Counter} the counter on this CPU with the given category/name
90     * combination, or undefined if it doesn't exist.
91     */
92    getCounter: function(cat, name) {
93      var id = cat + '.' + name;
94      if (!this.counters[id])
95        return undefined;
96      return this.counters[id];
97    },
98
99    /**
100     * Shifts all the timestamps inside this CPU forward by the amount
101     * specified.
102     */
103    shiftTimestampsForward: function(amount) {
104      for (var sI = 0; sI < this.slices.length; sI++)
105        this.slices[sI].start = (this.slices[sI].start + amount);
106      for (var id in this.counters)
107        this.counters[id].shiftTimestampsForward(amount);
108    },
109
110    /**
111     * Updates the range based on the current slices attached to the cpu.
112     */
113    updateBounds: function() {
114      this.bounds.reset();
115      if (this.slices.length) {
116        this.bounds.addValue(this.slices[0].start);
117        this.bounds.addValue(this.slices[this.slices.length - 1].end);
118      }
119      for (var id in this.counters) {
120        this.counters[id].updateBounds();
121        this.bounds.addRange(this.counters[id].bounds);
122      }
123      if (this.samples_ && this.samples_.length) {
124        this.bounds.addValue(this.samples_[0].start);
125        this.bounds.addValue(
126            this.samples_[this.samples_.length - 1].end);
127      }
128    },
129
130    createSubSlices: function() {
131      this.samples_ = this.kernel.model.samples.filter(function(sample) {
132        return sample.cpu == this;
133      }, this);
134    },
135
136    addCategoriesToDict: function(categoriesDict) {
137      for (var i = 0; i < this.slices.length; i++)
138        categoriesDict[this.slices[i].category] = true;
139      for (var id in this.counters)
140        categoriesDict[this.counters[id].category] = true;
141      for (var i = 0; i < this.samples_.length; i++)
142        categoriesDict[this.samples_[i].category] = true;
143    },
144
145
146
147    /*
148     * Returns the index of the slice in the CPU's slices, or undefined.
149     */
150    indexOf: function(cpuSlice) {
151      var i = tr.b.findLowIndexInSortedArray(
152          this.slices,
153          function(slice) { return slice.start; },
154          cpuSlice.start);
155      if (this.slices[i] !== cpuSlice)
156        return undefined;
157      return i;
158    },
159
160    /**
161     * Closes the thread running on the CPU. |end_timestamp| is the timestamp
162     * at which the thread was unscheduled. |args| is merged with the arguments
163     * specified when the thread was initially scheduled.
164     */
165    closeActiveThread: function(end_timestamp, args) {
166      // Don't generate a slice if the last active thread is the idle task.
167      if (this.lastActiveThread_ == undefined || this.lastActiveThread_ == 0)
168        return;
169
170      if (end_timestamp < this.lastActiveTimestamp_) {
171        throw new Error('The end timestamp of a thread running on CPU ' +
172                        this.cpuNumber + ' is before its start timestamp.');
173      }
174
175      // Merge |args| with |this.lastActiveArgs_|. If a key is in both
176      // dictionaries, the value from |args| is used.
177      for (var key in args) {
178        this.lastActiveArgs_[key] = args[key];
179      }
180
181      var duration = end_timestamp - this.lastActiveTimestamp_;
182      var slice = new tr.model.CpuSlice(
183          '', this.lastActiveName_,
184          ColorScheme.getColorIdForGeneralPurposeString(this.lastActiveName_),
185          this.lastActiveTimestamp_,
186          this.lastActiveArgs_,
187          duration);
188      slice.cpu = this;
189      this.slices.push(slice);
190
191      // Clear the last state.
192      this.lastActiveTimestamp_ = undefined;
193      this.lastActiveThread_ = undefined;
194      this.lastActiveName_ = undefined;
195      this.lastActiveArgs_ = undefined;
196    },
197
198    switchActiveThread: function(timestamp, old_thread_args, new_thread_id,
199                                 new_thread_name, new_thread_args) {
200      // Close the previous active thread and generate a slice.
201      this.closeActiveThread(timestamp, old_thread_args);
202
203      // Keep track of the new thread.
204      this.lastActiveTimestamp_ = timestamp;
205      this.lastActiveThread_ = new_thread_id;
206      this.lastActiveName_ = new_thread_name;
207      this.lastActiveArgs_ = new_thread_args;
208    },
209
210    /**
211     * Returns the frequency statistics for this CPU;
212     * the returned object contains the frequencies as keys,
213     * and the duration at this frequency in milliseconds as the value,
214     * for the range that was specified.
215     */
216    getFreqStatsForRange: function(range) {
217      var stats = {};
218
219      function addStatsForFreq(freqSample, index) {
220        // Counters don't have an explicit end or duration;
221        // calculate the end by looking at the starting point
222        // of the next value in the series, or if that doesn't
223        // exist, assume this frequency is held until the end.
224        var freqEnd = (index < freqSample.series_.length - 1) ?
225            freqSample.series_.samples_[index + 1].timestamp : range.max;
226
227        var freqRange = tr.b.Range.fromExplicitRange(freqSample.timestamp,
228            freqEnd);
229        var intersection = freqRange.findIntersection(range);
230        if (!(freqSample.value in stats))
231          stats[freqSample.value] = 0;
232        stats[freqSample.value] += intersection.duration;
233      }
234
235      var freqCounter = this.getCounter('', 'Clock Frequency');
236      if (freqCounter !== undefined) {
237        var freqSeries = freqCounter.getSeries(0);
238        if (!freqSeries)
239          return;
240
241        tr.b.iterateOverIntersectingIntervals(freqSeries.samples_,
242            function(x) { return x.timestamp; },
243            function(x, index) { return index < freqSeries.length - 1 ?
244                                     freqSeries.samples_[index + 1].timestamp :
245                                     range.max; },
246            range.min,
247            range.max,
248            addStatsForFreq);
249      }
250
251      return stats;
252    }
253  };
254
255  /**
256   * Comparison between processes that orders by cpuNumber.
257   */
258  Cpu.compare = function(x, y) {
259    return x.cpuNumber - y.cpuNumber;
260  };
261
262
263  return {
264    Cpu: Cpu
265  };
266});
267</script>
268