Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(311)

Side by Side Diff: chrome/browser/resources/gpu_internals/timeline_model.js

Issue 7555005: Moving the contents of chrome://gpu Profiling to chrome://tracing. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: rebase Created 9 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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 /**
69 * Name of the thread, if present.
70 */
71 name: undefined,
72
73 getSubrow: function(i) {
74 while (i >= this.subRows.length)
75 this.subRows.push([]);
76 return this.subRows[i];
77 },
78
79 addNonNestedSlice: function(slice) {
80 for (var i = 0; i < this.nonNestedSubRows.length; i++) {
81 var currSubRow = this.nonNestedSubRows[i];
82 var lastSlice = currSubRow[currSubRow.length - 1];
83 if (slice.start >= lastSlice.start + lastSlice.duration) {
84 currSubRow.push(slice);
85 return;
86 }
87 }
88 this.nonNestedSubRows.push([slice]);
89 },
90
91 /**
92 * Updates the minTimestamp and maxTimestamp fields based on the
93 * current slices and nonNestedSubRows attached to the thread.
94 */
95 updateBounds: function() {
96 var values = [];
97 var slices;
98 if (this.subRows[0].length != 0) {
99 slices = this.subRows[0];
100 values.push(slices[0].start);
101 values.push(slices[slices.length - 1].end);
102 }
103 for (var i = 0; i < this.nonNestedSubRows.length; ++i) {
104 slices = this.nonNestedSubRows[i];
105 values.push(slices[0].start);
106 values.push(slices[slices.length - 1].end);
107 }
108 if (values.length) {
109 this.minTimestamp = Math.min.apply(Math, values);
110 this.maxTimestamp = Math.max.apply(Math, values);
111 } else {
112 this.minTimestamp = undefined;
113 this.maxTimestamp = undefined;
114 }
115 }
116
117 };
118
119 /**
120 * Comparison between threads that orders first by pid,
121 * then by names, then by tid.
122 */
123 TimelineThread.compare = function(x,y) {
124 if(x.parent.pid != y.parent.pid) {
125 return x.parent.pid - y.parent.pid;
126 }
127
128 if (x.name && y.name) {
129 var tmp = x.name.localeCompare(y.name);
130 if (tmp == 0)
131 return x.tid - y.tid;
132 return tmp;
133 } else if (x.name) {
134 return -1;
135 } else if (y.name){
136 return 1;
137 } else {
138 return x.tid - y.tid;
139 }
140 };
141
142
143 /**
144 * The TimelineProcess represents a single process in the
145 * trace. Right now, we keep this around purely for bookkeeping
146 * reasons.
147 * @constructor
148 */
149 function TimelineProcess(pid) {
150 this.pid = pid;
151 this.threads = {};
152 };
153
154 TimelineProcess.prototype = {
155 getThread: function(tid) {
156 if (!this.threads[tid])
157 this.threads[tid] = new TimelineThread(this, tid);
158 return this.threads[tid];
159 }
160 };
161
162 /**
163 * Builds a model from an array of TraceEvent objects.
164 * @param {Array} events An array of TraceEvents created by
165 * TraceEvent.ToJSON().
166 * @constructor
167 */
168 function TimelineModel(events) {
169 this.processes = {};
170 this.importErrors = [];
171
172 if (events)
173 this.importEvents(events);
174 }
175
176 TimelineModel.prototype = {
177 __proto__: cr.EventTarget.prototype,
178
179 getProcess: function(pid) {
180 if (!this.processes[pid])
181 this.processes[pid] = new TimelineProcess(pid);
182 return this.processes[pid];
183 },
184
185 /**
186 * The import takes an array of json-ified TraceEvents and adds them into
187 * the TimelineModel as processes, threads, and slices.
188 */
189 importEvents: function(events) {
190 // A ptid is a pid and tid joined together x:y fashion, eg 1024:130
191 // The ptid is a unique key for a thread in the trace.
192 this.importErrors = [];
193
194 // Threadstate
195 const numColorIds = 30;
196 function ThreadState(tid) {
197 this.openSlices = [];
198 this.openNonNestedSlices = {};
199 }
200 var threadStateByPTID = {};
201
202 var nameToColorMap = {};
203 function getColor(name) {
204 if (!(name in nameToColorMap)) {
205 // Compute a simplistic hashcode of the string so we get consistent
206 // coloring across traces.
207 var hash = 0;
208 for (var i = 0; i < name.length; ++i)
209 hash = (hash + 37 * hash + 11 * name.charCodeAt(i)) % 0xFFFFFFFF;
210 nameToColorMap[name] = hash % numColorIds;
211 }
212 return nameToColorMap[name];
213 }
214
215 var self = this;
216
217 /**
218 * Helper to process a 'begin' event (e.g. initiate a slice).
219 * @param {ThreadState} state Thread state (holds slices).
220 * @param {Object} event The current trace event.
221 */
222 function processBegin(state, event) {
223 var colorId = getColor(event.name);
224 var slice =
225 { index: eI,
226 slice: new TimelineSlice(event.name, colorId, event.ts,
227 event.args) };
228 if (event.args['ui-nest'] === '0') {
229 var sliceID = event.name;
230 for (var x in event.args)
231 sliceID += ';' + event.args[x];
232 if (state.openNonNestedSlices[sliceID])
233 this.importErrors.push('Event ' + sliceID + ' already open.');
234 state.openNonNestedSlices[sliceID] = slice;
235 } else {
236 state.openSlices.push(slice);
237 }
238 }
239
240 /**
241 * Helper to process an 'end' event (e.g. close a slice).
242 * @param {ThreadState} state Thread state (holds slices).
243 * @param {Object} event The current trace event.
244 */
245 function processEnd(state, event) {
246 if (event.args['ui-nest'] === '0') {
247 var sliceID = event.name;
248 for (var x in event.args)
249 sliceID += ';' + event.args[x];
250 var slice = state.openNonNestedSlices[sliceID];
251 if (!slice)
252 return;
253 slice.slice.duration = event.ts - slice.slice.start;
254
255 // Store the slice in a non-nested subrow.
256 var thread = self.getProcess(event.pid).getThread(event.tid);
257 thread.addNonNestedSlice(slice.slice);
258 delete state.openNonNestedSlices[name];
259 } else {
260 if (state.openSlices.length == 0) {
261 // Ignore E events that are unmatched.
262 return;
263 }
264 var slice = state.openSlices.pop().slice;
265 slice.duration = event.ts - slice.start;
266
267 // Store the slice on the correct subrow.
268 var thread = self.getProcess(event.pid).getThread(event.tid);
269 var subRowIndex = state.openSlices.length;
270 thread.getSubrow(subRowIndex).push(slice);
271
272 // Add the slice to the subSlices array of its parent.
273 if (state.openSlices.length) {
274 var parentSlice = state.openSlices[state.openSlices.length - 1];
275 parentSlice.slice.subSlices.push(slice);
276 }
277 }
278 }
279
280 // Walk through events
281 for (var eI = 0; eI < events.length; eI++) {
282 var event = events[eI];
283 var ptid = event.pid + ':' + event.tid;
284
285 if (!(ptid in threadStateByPTID))
286 threadStateByPTID[ptid] = new ThreadState();
287 var state = threadStateByPTID[ptid];
288
289 if (event.ph == 'B') {
290 processBegin(state, event);
291 } else if (event.ph == 'E') {
292 processEnd(state, event);
293 } else if (event.ph == 'I') {
294 // Treat an Instant event as a duration 0 slice.
295 // TimelineSliceTrack's redraw() knows how to handle this.
296 processBegin(state, event);
297 processEnd(state, event);
298 } else if (event.ph == 'M') {
299 if (event.name == 'thread_name') {
300 var thread = this.getProcess(event.pid).getThread(event.tid);
301 thread.name = event.args.name;
302 } else {
303 this.importErrors.push('Unrecognized metadata name: ' + event.name);
304 }
305 } else {
306 this.importErrors.push('Unrecognized event phase: ' + event.ph +
307 '(' + event.name + ')');
308 }
309 }
310 this.pruneEmptyThreads();
311 this.updateBounds();
312
313 // Add end events for any events that are still on the stack. These
314 // are events that were still open when trace was ended, and can often
315 // indicate deadlock behavior.
316 for (var ptid in threadStateByPTID) {
317 var state = threadStateByPTID[ptid];
318 while (state.openSlices.length > 0) {
319 var slice = state.openSlices.pop();
320 slice.slice.duration = this.maxTimestamp - slice.slice.start;
321 slice.slice.didNotFinish = true;
322 var event = events[slice.index];
323
324 // Store the slice on the correct subrow.
325 var thread = this.getProcess(event.pid).getThread(event.tid);
326 var subRowIndex = state.openSlices.length;
327 thread.getSubrow(subRowIndex).push(slice.slice);
328
329 // Add the slice to the subSlices array of its parent.
330 if (state.openSlices.length) {
331 var parentSlice = state.openSlices[state.openSlices.length - 1];
332 parentSlice.slice.subSlices.push(slice.slice);
333 }
334 }
335 }
336
337 this.shiftWorldToMicroseconds();
338
339 var boost = (this.maxTimestamp - this.minTimestamp) * 0.15;
340 this.minTimestamp = this.minTimestamp - boost;
341 this.maxTimestamp = this.maxTimestamp + boost;
342 },
343
344 /**
345 * Removes threads from the model that have no subrows.
346 */
347 pruneEmptyThreads: function() {
348 for (var pid in this.processes) {
349 var process = this.processes[pid];
350 var prunedThreads = [];
351 for (var tid in process.threads) {
352 var thread = process.threads[tid];
353 if (thread.subRows[0].length || thread.nonNestedSubRows.legnth)
354 prunedThreads.push(thread);
355 }
356 process.threads = prunedThreads;
357 }
358 },
359
360 updateBounds: function() {
361 var wmin = Infinity;
362 var wmax = -wmin;
363 var threads = this.getAllThreads();
364 for (var tI = 0; tI < threads.length; tI++) {
365 var thread = threads[tI];
366 thread.updateBounds();
367 if (thread.minTimestamp != undefined &&
368 thread.maxTimestamp != undefined) {
369 wmin = Math.min(wmin, thread.minTimestamp);
370 wmax = Math.max(wmax, thread.maxTimestamp);
371 }
372 }
373 this.minTimestamp = wmin;
374 this.maxTimestamp = wmax;
375 },
376
377 shiftWorldToMicroseconds: function() {
378 var timeBase = this.minTimestamp;
379 var threads = this.getAllThreads();
380 for (var tI = 0; tI < threads.length; tI++) {
381 var thread = threads[tI];
382 var shiftSubRow = function(subRow) {
383 for (var tS = 0; tS < subRow.length; tS++) {
384 var slice = subRow[tS];
385 slice.start = (slice.start - timeBase) / 1000;
386 slice.duration /= 1000;
387 }
388 };
389 for (var tSR = 0; tSR < thread.subRows.length; tSR++) {
390 shiftSubRow(thread.subRows[tSR]);
391 }
392 for (var tSR = 0; tSR < thread.nonNestedSubRows.length; tSR++) {
393 shiftSubRow(thread.nonNestedSubRows[tSR]);
394 }
395 }
396
397 this.updateBounds();
398 },
399
400 getAllThreads: function() {
401 var threads = [];
402 for (var pid in this.processes) {
403 var process = this.processes[pid];
404 for (var tid in process.threads) {
405 threads.push(process.threads[tid]);
406 }
407 }
408 return threads;
409 }
410
411 };
412
413 return {
414 TimelineSlice: TimelineSlice,
415 TimelineThread: TimelineThread,
416 TimelineProcess: TimelineProcess,
417 TimelineModel: TimelineModel
418 };
419 });
OLDNEW
« no previous file with comments | « chrome/browser/resources/gpu_internals/timeline.js ('k') | chrome/browser/resources/gpu_internals/timeline_track.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698