1// Copyright (c) 2011 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
6/**
7 * @fileoverview TimelineModel is a parsed representation of the
8 * TraceEvents obtained from base/trace_event in which the begin-end
9 * tokens are converted into a hierarchy of processes, threads,
10 * subrows, and slices.
11 *
12 * The building block of the model is a slice. A slice is roughly
13 * equivalent to function call executing on a specific thread. As a
14 * result, slices may have one or more subslices.
15 *
16 * A thread contains one or more subrows of slices. Row 0 corresponds to
17 * the "root" slices, e.g. the topmost slices. Row 1 contains slices that
18 * are nested 1 deep in the stack, and so on. We use these subrows to draw
19 * nesting tasks.
20 *
21 */
22cr.define('gpu', function() {
23  /**
24   * A TimelineSlice represents an interval of time on a given thread
25   * associated with a specific trace event. For example,
26   *   TRACE_EVENT_BEGIN1("x","myArg", 7) at time=0.1ms
27   *   TRACE_EVENT_END()                  at time=0.3ms
28   * Results in a single timeline slice from 0.1 with duration 0.2.
29   *
30   * All time units are stored in milliseconds.
31   * @constructor
32   */
33  function TimelineSlice(title, colorId, start, args) {
34    this.title = title;
35    this.start = start;
36    this.colorId = colorId;
37    this.args = args;
38    this.subSlices = [];
39  }
40
41  TimelineSlice.prototype = {
42    selected: false,
43
44    duration: undefined,
45
46    get end() {
47      return this.start + this.duration;
48    }
49  };
50
51  /**
52   * A TimelineThread stores all the trace events collected for a particular
53   * thread. We organize the slices on a thread by "subrows," where subrow 0
54   * has all the root slices, subrow 1 those nested 1 deep, and so on.
55   *
56   * @constructor
57   */
58  function TimelineThread(parent, tid) {
59    this.parent = parent;
60    this.tid = tid;
61    this.subRows = [[]];
62  }
63
64  TimelineThread.prototype = {
65    getSubrow: function(i) {
66      while (i >= this.subRows.length)
67        this.subRows.push([]);
68      return this.subRows[i];
69    },
70
71    updateBounds: function() {
72      var slices = this.subRows[0];
73      if (slices.length != 0) {
74        this.minTimestamp = slices[0].start;
75        this.maxTimestamp = slices[slices.length - 1].end;
76      } else {
77        this.minTimestamp = undefined;
78        this.maxTimestamp = undefined;
79      }
80    }
81
82  };
83
84  /**
85   * The TimelineProcess represents a single process in the
86   * trace. Right now, we keep this around purely for bookkeeping
87   * reasons.
88   * @constructor
89   */
90  function TimelineProcess(pid) {
91    this.pid = pid;
92    this.threads = {};
93  };
94
95  TimelineProcess.prototype = {
96    getThread: function(tid) {
97      if (!this.threads[tid])
98        this.threads[tid] = new TimelineThread(this, tid);
99      return this.threads[tid];
100    }
101  };
102
103  /**
104   * Builds a model from an array of TraceEvent objects.
105   * @param {Array} events An array of TraceEvents created by
106   *     TraceEvent.ToJSON().
107   * @constructor
108   */
109  function TimelineModel(events) {
110    this.processes = {};
111
112    if (events)
113      this.importEvents(events);
114  }
115
116  TimelineModel.prototype = {
117    __proto__: cr.EventTarget.prototype,
118
119    getProcess: function(pid) {
120      if (!this.processes[pid])
121        this.processes[pid] = new TimelineProcess(pid);
122      return this.processes[pid];
123    },
124
125    /**
126     * The import takes an array of json-ified TraceEvents and adds them into
127     * the TimelineModel as processes, threads, and slices.
128     */
129    importEvents: function(events) {
130      // A ptid is a pid and tid joined together x:y fashion, eg 1024:130
131      // The ptid is a unique key for a thread in the trace.
132
133
134      // Threadstate
135      const numColorIds = 12;
136      function ThreadState(tid) {
137        this.openSlices = [];
138      }
139      var threadStateByPTID = {};
140
141      var nameToColorMap = {};
142      function getColor(name) {
143        if (!(name in nameToColorMap)) {
144          // Compute a simplistic hashcode of the string so we get consistent
145          // coloring across traces.
146          var hash = 0;
147          for (var i = 0; i < name.length; ++i)
148            hash = (hash + 37 * hash + name.charCodeAt(i)) % 0xFFFFFFFF;
149          nameToColorMap[name] = hash % numColorIds;
150        }
151        return nameToColorMap[name];
152      }
153
154      // Walk through events
155      for (var eI = 0; eI < events.length; eI++) {
156        var event = events[eI];
157        var ptid = event.pid + ':' + event.tid;
158        if (!(ptid in threadStateByPTID)) {
159          threadStateByPTID[ptid] = new ThreadState();
160        }
161        var state = threadStateByPTID[ptid];
162        if (event.ph == 'B') {
163          var colorId = getColor(event.name);
164          var slice = new TimelineSlice(event.name, colorId, event.ts,
165                                        event.args);
166          state.openSlices.push(slice);
167        } else if (event.ph == 'E') {
168          if (state.openSlices.length == 0) {
169            // Ignore E events that that are unmatched.
170            continue;
171          }
172          var slice = state.openSlices.pop();
173          slice.duration = event.ts - slice.start;
174
175          // Store the slice on the right subrow.
176          var thread = this.getProcess(event.pid).getThread(event.tid);
177          var subRowIndex = state.openSlices.length;
178          thread.getSubrow(subRowIndex).push(slice);
179
180          // Add the slice to the subSlices array of its parent.
181          if (state.openSlices.length) {
182            var parentSlice = state.openSlices[state.openSlices.length - 1];
183            parentSlice.subSlices.push(slice);
184          }
185        } else if (event.ph == 'I') {
186          // TODO(nduca): Implement parsing of immediate events.
187          console.log('Parsing of I-type events not implemented.');
188        } else {
189          throw new Error('Unrecognized event phase: ' + event.ph +
190                          '(' + event.name + ')');
191        }
192      }
193      this.updateBounds();
194
195      this.shiftWorldToMicroseconds();
196
197      var boost = (this.maxTimestamp - this.minTimestamp) * 0.15;
198      this.minTimestamp = this.minTimestamp - boost;
199      this.maxTimestamp = this.maxTimestamp + boost;
200    },
201
202    updateBounds: function() {
203      var wmin = Infinity;
204      var wmax = -wmin;
205      var threads = this.getAllThreads();
206      for (var tI = 0; tI < threads.length; tI++) {
207        var thread = threads[tI];
208        thread.updateBounds();
209        wmin = Math.min(wmin, thread.minTimestamp);
210        wmax = Math.max(wmax, thread.maxTimestamp);
211      }
212      this.minTimestamp = wmin;
213      this.maxTimestamp = wmax;
214    },
215
216    shiftWorldToMicroseconds: function() {
217      var timeBase = this.minTimestamp;
218      var threads = this.getAllThreads();
219      for (var tI = 0; tI < threads.length; tI++) {
220        var thread = threads[tI];
221        for (var tSR = 0; tSR < thread.subRows.length; tSR++) {
222          var subRow = thread.subRows[tSR];
223          for (var tS = 0; tS < subRow.length; tS++) {
224            var slice = subRow[tS];
225            slice.start = (slice.start - timeBase) / 1000;
226            slice.duration /= 1000;
227          }
228        }
229      }
230
231      this.updateBounds();
232    },
233
234    getAllThreads: function() {
235      var threads = [];
236      for (var pid in this.processes) {
237        var process = this.processes[pid];
238        for (var tid in process.threads) {
239          threads.push(process.threads[tid]);
240        }
241      }
242      return threads;
243    }
244
245  };
246
247  return {
248    TimelineSlice: TimelineSlice,
249    TimelineThread: TimelineThread,
250    TimelineProcess: TimelineProcess,
251    TimelineModel: TimelineModel
252  };
253});
254