OLD | NEW |
| (Empty) |
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 */ | |
22 cr.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.didNotFinish = false; | |
39 this.subSlices = []; | |
40 } | |
41 | |
42 TimelineSlice.prototype = { | |
43 selected: false, | |
44 | |
45 duration: undefined, | |
46 | |
47 get end() { | |
48 return this.start + this.duration; | |
49 } | |
50 }; | |
51 | |
52 /** | |
53 * A TimelineThread stores all the trace events collected for a particular | |
54 * thread. We organize the slices on a thread by "subrows," where subrow 0 | |
55 * has all the root slices, subrow 1 those nested 1 deep, and so on. There | |
56 * is also a set of non-nested subrows. | |
57 * | |
58 * @constructor | |
59 */ | |
60 function TimelineThread(parent, tid) { | |
61 this.parent = parent; | |
62 this.tid = tid; | |
63 this.subRows = [[]]; | |
64 this.nonNestedSubRows = []; | |
65 } | |
66 | |
67 TimelineThread.prototype = { | |
68 getSubrow: function(i) { | |
69 while (i >= this.subRows.length) | |
70 this.subRows.push([]); | |
71 return this.subRows[i]; | |
72 }, | |
73 | |
74 addNonNestedSlice: function(slice) { | |
75 for (var i = 0; i < this.nonNestedSubRows.length; i++) { | |
76 var currSubRow = this.nonNestedSubRows[i]; | |
77 var lastSlice = currSubRow[currSubRow.length - 1]; | |
78 if (slice.start >= lastSlice.start + lastSlice.duration) { | |
79 currSubRow.push(slice); | |
80 return; | |
81 } | |
82 } | |
83 this.nonNestedSubRows.push([slice]); | |
84 }, | |
85 | |
86 updateBounds: function() { | |
87 var slices = this.subRows[0]; | |
88 if (slices.length != 0) { | |
89 this.minTimestamp = slices[0].start; | |
90 this.maxTimestamp = slices[slices.length - 1].end; | |
91 } else { | |
92 this.minTimestamp = undefined; | |
93 this.maxTimestamp = undefined; | |
94 } | |
95 } | |
96 | |
97 }; | |
98 | |
99 /** | |
100 * The TimelineProcess represents a single process in the | |
101 * trace. Right now, we keep this around purely for bookkeeping | |
102 * reasons. | |
103 * @constructor | |
104 */ | |
105 function TimelineProcess(pid) { | |
106 this.pid = pid; | |
107 this.threads = {}; | |
108 }; | |
109 | |
110 TimelineProcess.prototype = { | |
111 getThread: function(tid) { | |
112 if (!this.threads[tid]) | |
113 this.threads[tid] = new TimelineThread(this, tid); | |
114 return this.threads[tid]; | |
115 } | |
116 }; | |
117 | |
118 /** | |
119 * Builds a model from an array of TraceEvent objects. | |
120 * @param {Array} events An array of TraceEvents created by | |
121 * TraceEvent.ToJSON(). | |
122 * @constructor | |
123 */ | |
124 function TimelineModel(events) { | |
125 this.processes = {}; | |
126 | |
127 if (events) | |
128 this.importEvents(events); | |
129 } | |
130 | |
131 TimelineModel.prototype = { | |
132 __proto__: cr.EventTarget.prototype, | |
133 | |
134 getProcess: function(pid) { | |
135 if (!this.processes[pid]) | |
136 this.processes[pid] = new TimelineProcess(pid); | |
137 return this.processes[pid]; | |
138 }, | |
139 | |
140 /** | |
141 * The import takes an array of json-ified TraceEvents and adds them into | |
142 * the TimelineModel as processes, threads, and slices. | |
143 */ | |
144 importEvents: function(events) { | |
145 // A ptid is a pid and tid joined together x:y fashion, eg 1024:130 | |
146 // The ptid is a unique key for a thread in the trace. | |
147 | |
148 | |
149 // Threadstate | |
150 const numColorIds = 30; | |
151 function ThreadState(tid) { | |
152 this.openSlices = []; | |
153 this.openNonNestedSlices = {}; | |
154 } | |
155 var threadStateByPTID = {}; | |
156 | |
157 var nameToColorMap = {}; | |
158 function getColor(name) { | |
159 if (!(name in nameToColorMap)) { | |
160 // Compute a simplistic hashcode of the string so we get consistent | |
161 // coloring across traces. | |
162 var hash = 0; | |
163 for (var i = 0; i < name.length; ++i) | |
164 hash = (hash + 37 * hash + 11 * name.charCodeAt(i)) % 0xFFFFFFFF; | |
165 nameToColorMap[name] = hash % numColorIds; | |
166 } | |
167 return nameToColorMap[name]; | |
168 } | |
169 | |
170 var self = this; | |
171 | |
172 /** | |
173 * Helper to process a 'begin' event (e.g. initiate a slice). | |
174 * @param {ThreadState} state Thread state (holds slices). | |
175 * @param {Object} event The current trace event. | |
176 */ | |
177 function processBegin(state, event) { | |
178 var colorId = getColor(event.name); | |
179 var slice = | |
180 { index: eI, | |
181 slice: new TimelineSlice(event.name, colorId, event.ts, | |
182 event.args) }; | |
183 if (event.args['ui-nest'] === '0') { | |
184 var sliceID = event.name; | |
185 for (var x in event.args) | |
186 sliceID += ';' + event.args[x]; | |
187 if (state.openNonNestedSlices[sliceID]) | |
188 console.log('Event ' + sliceID + ' already open.'); | |
189 state.openNonNestedSlices[sliceID] = slice; | |
190 } else { | |
191 state.openSlices.push(slice); | |
192 } | |
193 } | |
194 | |
195 /** | |
196 * Helper to process an 'end' event (e.g. close a slice). | |
197 * @param {ThreadState} state Thread state (holds slices). | |
198 * @param {Object} event The current trace event. | |
199 */ | |
200 function processEnd(state, event) { | |
201 if (event.args['ui-nest'] === '0') { | |
202 var sliceID = event.name; | |
203 for (var x in event.args) | |
204 sliceID += ';' + event.args[x]; | |
205 var slice = state.openNonNestedSlices[sliceID]; | |
206 if (!slice) | |
207 return; | |
208 slice.slice.duration = event.ts - slice.slice.start; | |
209 | |
210 // Store the slice in a non-nested subrow. | |
211 var thread = self.getProcess(event.pid).getThread(event.tid); | |
212 thread.addNonNestedSlice(slice.slice); | |
213 delete state.openNonNestedSlices[name]; | |
214 } else { | |
215 if (state.openSlices.length == 0) { | |
216 // Ignore E events that are unmatched. | |
217 return; | |
218 } | |
219 var slice = state.openSlices.pop().slice; | |
220 slice.duration = event.ts - slice.start; | |
221 | |
222 // Store the slice on the correct subrow. | |
223 var thread = self.getProcess(event.pid).getThread(event.tid); | |
224 var subRowIndex = state.openSlices.length; | |
225 thread.getSubrow(subRowIndex).push(slice); | |
226 | |
227 // Add the slice to the subSlices array of its parent. | |
228 if (state.openSlices.length) { | |
229 var parentSlice = state.openSlices[state.openSlices.length - 1]; | |
230 parentSlice.slice.subSlices.push(slice); | |
231 } | |
232 } | |
233 } | |
234 | |
235 // Walk through events | |
236 for (var eI = 0; eI < events.length; eI++) { | |
237 var event = events[eI]; | |
238 var ptid = event.pid + ':' + event.tid; | |
239 | |
240 if (!(ptid in threadStateByPTID)) | |
241 threadStateByPTID[ptid] = new ThreadState(); | |
242 var state = threadStateByPTID[ptid]; | |
243 | |
244 if (event.ph == 'B') { | |
245 processBegin(state, event); | |
246 } else if (event.ph == 'E') { | |
247 processEnd(state, event); | |
248 } else if (event.ph == 'I') { | |
249 // Treat an Instant event as a duration 0 slice. | |
250 // TimelineSliceTrack's redraw() knows how to handle this. | |
251 processBegin(state, event); | |
252 processEnd(state, event); | |
253 } else { | |
254 throw new Error('Unrecognized event phase: ' + event.ph + | |
255 '(' + event.name + ')'); | |
256 } | |
257 } | |
258 | |
259 this.updateBounds(); | |
260 | |
261 // Add end events for any events that are still on the stack. These | |
262 // are events that were still open when trace was ended, and can often | |
263 // indicate deadlock behavior. | |
264 for (var ptid in threadStateByPTID) { | |
265 var state = threadStateByPTID[ptid]; | |
266 while (state.openSlices.length > 0) { | |
267 var slice = state.openSlices.pop(); | |
268 slice.slice.duration = this.maxTimestamp - slice.slice.start; | |
269 slice.slice.didNotFinish = true; | |
270 var event = events[slice.index]; | |
271 | |
272 // Store the slice on the correct subrow. | |
273 var thread = this.getProcess(event.pid).getThread(event.tid); | |
274 var subRowIndex = state.openSlices.length; | |
275 thread.getSubrow(subRowIndex).push(slice.slice); | |
276 | |
277 // Add the slice to the subSlices array of its parent. | |
278 if (state.openSlices.length) { | |
279 var parentSlice = state.openSlices[state.openSlices.length - 1]; | |
280 parentSlice.slice.subSlices.push(slice.slice); | |
281 } | |
282 } | |
283 } | |
284 | |
285 this.shiftWorldToMicroseconds(); | |
286 | |
287 var boost = (this.maxTimestamp - this.minTimestamp) * 0.15; | |
288 this.minTimestamp = this.minTimestamp - boost; | |
289 this.maxTimestamp = this.maxTimestamp + boost; | |
290 }, | |
291 | |
292 updateBounds: function() { | |
293 var wmin = Infinity; | |
294 var wmax = -wmin; | |
295 var threads = this.getAllThreads(); | |
296 for (var tI = 0; tI < threads.length; tI++) { | |
297 var thread = threads[tI]; | |
298 thread.updateBounds(); | |
299 if (thread.minTimestamp && thread.maxTimestamp) { | |
300 wmin = Math.min(wmin, thread.minTimestamp); | |
301 wmax = Math.max(wmax, thread.maxTimestamp); | |
302 } | |
303 } | |
304 this.minTimestamp = wmin; | |
305 this.maxTimestamp = wmax; | |
306 }, | |
307 | |
308 shiftWorldToMicroseconds: function() { | |
309 var timeBase = this.minTimestamp; | |
310 var threads = this.getAllThreads(); | |
311 for (var tI = 0; tI < threads.length; tI++) { | |
312 var thread = threads[tI]; | |
313 var shiftSubRow = function(subRow) { | |
314 for (var tS = 0; tS < subRow.length; tS++) { | |
315 var slice = subRow[tS]; | |
316 slice.start = (slice.start - timeBase) / 1000; | |
317 slice.duration /= 1000; | |
318 } | |
319 }; | |
320 for (var tSR = 0; tSR < thread.subRows.length; tSR++) { | |
321 shiftSubRow(thread.subRows[tSR]); | |
322 } | |
323 for (var tSR = 0; tSR < thread.nonNestedSubRows.length; tSR++) { | |
324 shiftSubRow(thread.nonNestedSubRows[tSR]); | |
325 } | |
326 } | |
327 | |
328 this.updateBounds(); | |
329 }, | |
330 | |
331 getAllThreads: function() { | |
332 var threads = []; | |
333 for (var pid in this.processes) { | |
334 var process = this.processes[pid]; | |
335 for (var tid in process.threads) { | |
336 threads.push(process.threads[tid]); | |
337 } | |
338 } | |
339 return threads; | |
340 } | |
341 | |
342 }; | |
343 | |
344 return { | |
345 TimelineSlice: TimelineSlice, | |
346 TimelineThread: TimelineThread, | |
347 TimelineProcess: TimelineProcess, | |
348 TimelineModel: TimelineModel | |
349 }; | |
350 }); | |
OLD | NEW |