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

Side by Side Diff: chrome/browser/resources/tracing/trace_event_importer.js

Issue 9706010: about:tracing support for TRACE_ASYNC_START/FINISH events. (Closed) Base URL: http://git.chromium.org/chromium/src.git@master
Patch Set: try again Created 8 years, 9 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
OLDNEW
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 /** 5 /**
6 * @fileoverview TraceEventImporter imports TraceEvent-formatted data 6 * @fileoverview TraceEventImporter imports TraceEvent-formatted data
7 * into the provided timeline model. 7 * into the provided timeline model.
8 */ 8 */
9 cr.define('tracing', function() { 9 cr.define('tracing', function() {
10 function ThreadState(tid) { 10 function ThreadState(tid) {
11 this.openSlices = []; 11 this.openSlices = [];
12 this.openNonNestedSlices = {};
13 } 12 }
14 13
15 function TraceEventImporter(model, eventData) { 14 function TraceEventImporter(model, eventData) {
16 this.model_ = model; 15 this.model_ = model;
17 16
18 if (typeof(eventData) === 'string' || eventData instanceof String) { 17 if (typeof(eventData) === 'string' || eventData instanceof String) {
19 // If the event data begins with a [, then we know it should end with a ]. 18 // If the event data begins with a [, then we know it should end with a ].
20 // The reason we check for this is because some tracing implementations 19 // The reason we check for this is because some tracing implementations
21 // cannot guarantee that a ']' gets written to the trace file. So, we are 20 // cannot guarantee that a ']' gets written to the trace file. So, we are
22 // forgiving and if this is obviously the case, we fix it up before 21 // forgiving and if this is obviously the case, we fix it up before
(...skipping 19 matching lines...) Expand all
42 // inside a container. E.g { ... , traceEvents: [ ] } 41 // inside a container. E.g { ... , traceEvents: [ ] }
43 // 42 //
44 // If we see that, just pull out the trace events. 43 // If we see that, just pull out the trace events.
45 if (this.events_.traceEvents) 44 if (this.events_.traceEvents)
46 this.events_ = this.events_.traceEvents; 45 this.events_ = this.events_.traceEvents;
47 46
48 // To allow simple indexing of threads, we store all the threads by a 47 // To allow simple indexing of threads, we store all the threads by a
49 // PTID. A ptid is a pid and tid joined together x:y fashion, eg 48 // PTID. A ptid is a pid and tid joined together x:y fashion, eg
50 // 1024:130. The ptid is a unique key for a thread in the trace. 49 // 1024:130. The ptid is a unique key for a thread in the trace.
51 this.threadStateByPTID_ = {}; 50 this.threadStateByPTID_ = {};
51
52 // Async events need to be processed durign finalizeEvents
53 this.allAsyncEvents_ = [];
52 } 54 }
53 55
54 /** 56 /**
55 * @return {boolean} Whether obj is a TraceEvent array. 57 * @return {boolean} Whether obj is a TraceEvent array.
56 */ 58 */
57 TraceEventImporter.canImport = function(eventData) { 59 TraceEventImporter.canImport = function(eventData) {
58 // May be encoded JSON. But we dont want to parse it fully yet. 60 // May be encoded JSON. But we dont want to parse it fully yet.
59 // Use a simple heuristic: 61 // Use a simple heuristic:
60 // - eventData that starts with [ are probably trace_event 62 // - eventData that starts with [ are probably trace_event
61 // - eventData that starts with { are probably trace_event 63 // - eventData that starts with { are probably trace_event
(...skipping 16 matching lines...) Expand all
78 80
79 TraceEventImporter.prototype = { 81 TraceEventImporter.prototype = {
80 82
81 __proto__: Object.prototype, 83 __proto__: Object.prototype,
82 84
83 /** 85 /**
84 * Helper to process a 'begin' event (e.g. initiate a slice). 86 * Helper to process a 'begin' event (e.g. initiate a slice).
85 * @param {ThreadState} state Thread state (holds slices). 87 * @param {ThreadState} state Thread state (holds slices).
86 * @param {Object} event The current trace event. 88 * @param {Object} event The current trace event.
87 */ 89 */
88 processBegin: function(index, state, event) { 90 processBeginEvent: function(index, state, event) {
89 var colorId = tracing.getStringColorId(event.name); 91 var colorId = tracing.getStringColorId(event.name);
90 var slice = 92 var slice =
91 { index: index, 93 { index: index,
92 slice: new tracing.TimelineSlice(event.name, colorId, 94 slice: new tracing.TimelineThreadSlice(event.name, colorId,
93 event.ts / 1000, 95 event.ts / 1000,
94 event.args) }; 96 event.args) };
95 97
96 if (event.uts) 98 if (event.uts)
97 slice.slice.startInUserTime = event.uts / 1000; 99 slice.slice.startInUserTime = event.uts / 1000;
98 100
99 if (event.args['ui-nest'] === '0') { 101 if (event.args['ui-nest'] === '0') {
100 var sliceID = event.name; 102 this.model_.importErrors.push('ui-nest no longer supported.');
101 for (var x in event.args) 103 return;
102 sliceID += ';' + event.args[x];
103 if (state.openNonNestedSlices[sliceID])
104 this.model_.importErrors.push('Event ' + sliceID + ' already open.');
105 state.openNonNestedSlices[sliceID] = slice;
106 } else {
107 state.openSlices.push(slice);
108 } 104 }
105
106 state.openSlices.push(slice);
109 }, 107 },
110 108
111 /** 109 /**
112 * Helper to process an 'end' event (e.g. close a slice). 110 * Helper to process an 'end' event (e.g. close a slice).
113 * @param {ThreadState} state Thread state (holds slices). 111 * @param {ThreadState} state Thread state (holds slices).
114 * @param {Object} event The current trace event. 112 * @param {Object} event The current trace event.
115 */ 113 */
116 processEnd: function(state, event) { 114 processEndEvent: function(state, event) {
117 if (event.args['ui-nest'] === '0') { 115 if (event.args['ui-nest'] === '0') {
118 var sliceID = event.name; 116 this.model_.importErrors.push('ui-nest no longer supported.');
119 for (var x in event.args) 117 return;
120 sliceID += ';' + event.args[x]; 118 }
121 var slice = state.openNonNestedSlices[sliceID]; 119 if (state.openSlices.length == 0) {
122 if (!slice) 120 // Ignore E events that are unmatched.
123 return; 121 return;
124 slice.slice.duration = (event.ts / 1000) - slice.slice.start; 122 }
125 if (event.uts) 123 var slice = state.openSlices.pop().slice;
126 slice.durationInUserTime = (event.uts / 1000) - 124 slice.duration = (event.ts / 1000) - slice.start;
127 slice.slice.startInUserTime; 125 if (event.uts)
126 slice.durationInUserTime = (event.uts / 1000) - slice.startInUserTime;
128 127
129 // Store the slice in a non-nested subrow. 128 // Store the slice on the correct subrow.
130 var thread = 129 var thread = this.model_.getOrCreateProcess(event.pid).
131 this.model_.getOrCreateProcess(event.pid). 130 getOrCreateThread(event.tid);
132 getOrCreateThread(event.tid); 131 var subRowIndex = state.openSlices.length;
133 thread.addNonNestedSlice(slice.slice); 132 thread.getSubrow(subRowIndex).push(slice);
134 delete state.openNonNestedSlices[name];
135 } else {
136 if (state.openSlices.length == 0) {
137 // Ignore E events that are unmatched.
138 return;
139 }
140 var slice = state.openSlices.pop().slice;
141 slice.duration = (event.ts / 1000) - slice.start;
142 if (event.uts)
143 slice.durationInUserTime = (event.uts / 1000) - slice.startInUserTime;
144 133
145 // Store the slice on the correct subrow. 134 // Add the slice to the subSlices array of its parent.
146 var thread = this.model_.getOrCreateProcess(event.pid). 135 if (state.openSlices.length) {
147 getOrCreateThread(event.tid); 136 var parentSlice = state.openSlices[state.openSlices.length - 1];
148 var subRowIndex = state.openSlices.length; 137 parentSlice.slice.subSlices.push(slice);
149 thread.getSubrow(subRowIndex).push(slice); 138 }
139 },
150 140
151 // Add the slice to the subSlices array of its parent. 141 /**
152 if (state.openSlices.length) { 142 * Helper to process an 'async finish' event, which will close an open slice
153 var parentSlice = state.openSlices[state.openSlices.length - 1]; 143 * on a TimelineAsyncSliceGroup object.
154 parentSlice.slice.subSlices.push(slice); 144 **/
155 } 145 processAsyncEvent: function(index, state, event) {
156 } 146 var thread = this.model_.getOrCreateProcess(event.pid).
147 getOrCreateThread(event.tid);
148 this.allAsyncEvents_.push({
149 event: event,
150 thread: thread});
157 }, 151 },
158 152
159 /** 153 /**
160 * Helper function that closes any open slices. This happens when a trace 154 * Helper function that closes any open slices. This happens when a trace
161 * ends before an 'E' phase event can get posted. When that happens, this 155 * ends before an 'E' phase event can get posted. When that happens, this
162 * closes the slice at the highest timestamp we recorded and sets the 156 * closes the slice at the highest timestamp we recorded and sets the
163 * didNotFinish flag to true. 157 * didNotFinish flag to true.
164 */ 158 */
165 autoCloseOpenSlices: function() { 159 autoCloseOpenSlices: function() {
166 // We need to know the model bounds in order to assign an end-time to 160 // We need to know the model bounds in order to assign an end-time to
167 // the open slices. 161 // the open slices.
168 this.model_.updateBounds(); 162 this.model_.updateBounds();
169 163
170 // The model's max value in the trace is wrong at this point if there are 164 // The model's max value in the trace is wrong at this point if there are
171 // un-closed events. To close those events, we need the true global max 165 // un-closed events. To close those events, we need the true global max
172 // value. To compute this, build a list of timestamps that weren't 166 // value. To compute this, build a list of timestamps that weren't
173 // included in the max calculation, then compute the real maximum based 167 // included in the max calculation, then compute the real maximum based on
174 // on that. 168 // that.
175 var openTimestamps = []; 169 var openTimestamps = [];
176 for (var ptid in this.threadStateByPTID_) { 170 for (var ptid in this.threadStateByPTID_) {
177 var state = this.threadStateByPTID_[ptid]; 171 var state = this.threadStateByPTID_[ptid];
178 for (var i = 0; i < state.openSlices.length; i++) { 172 for (var i = 0; i < state.openSlices.length; i++) {
179 var slice = state.openSlices[i]; 173 var slice = state.openSlices[i];
180 openTimestamps.push(slice.slice.start); 174 openTimestamps.push(slice.slice.start);
181 for (var s = 0; s < slice.slice.subSlices.length; s++) { 175 for (var s = 0; s < slice.slice.subSlices.length; s++) {
182 var subSlice = slice.slice.subSlices[s]; 176 var subSlice = slice.slice.subSlices[s];
183 openTimestamps.push(subSlice.start); 177 openTimestamps.push(subSlice.start);
184 if (subSlice.duration) 178 if (subSlice.duration)
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
221 parentSlice.slice.subSlices.push(slice.slice); 215 parentSlice.slice.subSlices.push(slice.slice);
222 } 216 }
223 } 217 }
224 } 218 }
225 }, 219 },
226 220
227 /** 221 /**
228 * Helper that creates and adds samples to a TimelineCounter object based on 222 * Helper that creates and adds samples to a TimelineCounter object based on
229 * 'C' phase events. 223 * 'C' phase events.
230 */ 224 */
231 processCounter: function(event) { 225 processCounterEvent: function(event) {
232 var ctr_name; 226 var ctr_name;
233 if (event.id !== undefined) 227 if (event.id !== undefined)
234 ctr_name = event.name + '[' + event.id + ']'; 228 ctr_name = event.name + '[' + event.id + ']';
235 else 229 else
236 ctr_name = event.name; 230 ctr_name = event.name;
237 231
238 var ctr = this.model_.getOrCreateProcess(event.pid) 232 var ctr = this.model_.getOrCreateProcess(event.pid)
239 .getOrCreateCounter(event.cat, ctr_name); 233 .getOrCreateCounter(event.cat, ctr_name);
240 // Initialize the counter's series fields if needed. 234 // Initialize the counter's series fields if needed.
241 if (ctr.numSeries == 0) { 235 if (ctr.numSeries == 0) {
(...skipping 23 matching lines...) Expand all
265 } 259 }
266 }, 260 },
267 261
268 /** 262 /**
269 * Walks through the events_ list and outputs the structures discovered to 263 * Walks through the events_ list and outputs the structures discovered to
270 * model_. 264 * model_.
271 */ 265 */
272 importEvents: function() { 266 importEvents: function() {
273 // Walk through events 267 // Walk through events
274 var events = this.events_; 268 var events = this.events_;
269 // Some events cannot be handled until we have done a first pass over the
270 // data set. So, accumulate them into a temporary data structure.
271 var second_pass_events = [];
275 for (var eI = 0; eI < events.length; eI++) { 272 for (var eI = 0; eI < events.length; eI++) {
276 var event = events[eI]; 273 var event = events[eI];
277 var ptid = event.pid + ':' + event.tid; 274 var ptid = tracing.TimelineThread.getPTIDFromPidAndTid(
275 event.pid, event.tid);
278 276
279 if (!(ptid in this.threadStateByPTID_)) 277 if (!(ptid in this.threadStateByPTID_))
280 this.threadStateByPTID_[ptid] = new ThreadState(); 278 this.threadStateByPTID_[ptid] = new ThreadState();
281 var state = this.threadStateByPTID_[ptid]; 279 var state = this.threadStateByPTID_[ptid];
282 280
283 if (event.ph == 'B') { 281 if (event.ph == 'B') {
284 this.processBegin(eI, state, event); 282 this.processBeginEvent(eI, state, event);
285 } else if (event.ph == 'E') { 283 } else if (event.ph == 'E') {
286 this.processEnd(state, event); 284 this.processEndEvent(state, event);
285 } else if (event.ph == 'S') {
286 this.processAsyncEvent(eI, state, event);
287 } else if (event.ph == 'F') {
288 this.processAsyncEvent(eI, state, event);
287 } else if (event.ph == 'I') { 289 } else if (event.ph == 'I') {
288 // Treat an Instant event as a duration 0 slice. 290 // Treat an Instant event as a duration 0 slice.
289 // TimelineSliceTrack's redraw() knows how to handle this. 291 // TimelineSliceTrack's redraw() knows how to handle this.
290 this.processBegin(eI, state, event); 292 this.processBeginEvent(eI, state, event);
291 this.processEnd(state, event); 293 this.processEndEvent(state, event);
292 } else if (event.ph == 'C') { 294 } else if (event.ph == 'C') {
293 this.processCounter(event); 295 this.processCounterEvent(event);
294 } else if (event.ph == 'M') { 296 } else if (event.ph == 'M') {
295 if (event.name == 'thread_name') { 297 if (event.name == 'thread_name') {
296 var thread = this.model_.getOrCreateProcess(event.pid) 298 var thread = this.model_.getOrCreateProcess(event.pid)
297 .getOrCreateThread(event.tid); 299 .getOrCreateThread(event.tid);
298 thread.name = event.args.name; 300 thread.name = event.args.name;
299 } else { 301 } else {
300 this.model_.importErrors.push( 302 this.model_.importErrors.push(
301 'Unrecognized metadata name: ' + event.name); 303 'Unrecognized metadata name: ' + event.name);
302 } 304 }
303 } else { 305 } else {
304 this.model_.importErrors.push( 306 this.model_.importErrors.push(
305 'Unrecognized event phase: ' + event.ph + 307 'Unrecognized event phase: ' + event.ph +
306 '(' + event.name + ')'); 308 '(' + event.name + ')');
307 } 309 }
308 } 310 }
309 311
310 // Autoclose any open slices. 312 // Autoclose any open slices.
311 var hasOpenSlices = false; 313 var hasOpenSlices = false;
312 for (var ptid in this.threadStateByPTID_) { 314 for (var ptid in this.threadStateByPTID_) {
313 var state = this.threadStateByPTID_[ptid]; 315 var state = this.threadStateByPTID_[ptid];
314 hasOpenSlices |= state.openSlices.length > 0; 316 hasOpenSlices |= state.openSlices.length > 0;
315 } 317 }
316 if (hasOpenSlices) 318 if (hasOpenSlices)
317 this.autoCloseOpenSlices(); 319 this.autoCloseOpenSlices();
320 },
321
322 /**
323 * Called by the TimelineModel after all other importers have imported their
324 * events. This function creates async slices for any async events we saw.
325 */
326 finalizeImport: function() {
327 if (this.allAsyncEvents_.length == 0)
328 return;
329
330 this.allAsyncEvents_.sort(function(x, y) {
331 return x.event.ts - y.event.ts;
332 });
333
334 var startEventStatesByNameThenID = {};
335
336 var allAsyncEvents = this.allAsyncEvents_;
337 for (var i = 0; i < allAsyncEvents.length; i++) {
338 var asyncEventState = allAsyncEvents[i];
339
340 var event = asyncEventState.event;
341 var name = event.name;
342 if (name === undefined) {
343 this.model_.importErrors.push(
344 'Async events (ph: S or F) require an name parameter.');
345 continue;
346 }
347
348 var id = event.id;
349 if (id === undefined) {
350 this.model_.importErrors.push(
351 'Async events (ph: S or F) require an id parameter.');
352 continue;
353 }
354
355 if (event.ph == 'S') {
356 if (startEventStatesByNameThenID[name] === undefined)
357 startEventStatesByNameThenID[name] = {};
358 if (startEventStatesByNameThenID[name][id]) {
359 this.model_.importErrors.push(
360 'At ' + event.ts + ', an slice of the same id ' + id +
361 ' was alrady open.');
362 continue;
363 }
364 startEventStatesByNameThenID[name][id] = asyncEventState;
365 } else {
366 if (startEventStatesByNameThenID[name] === undefined) {
367 this.model_.importErrors.push(
368 'At ' + event.ts + ', no slice named ' + name +
369 ' was open.');
370 continue;
371 }
372 if (startEventStatesByNameThenID[name][id] === undefined) {
373 this.model_.importErrors.push(
374 'At ' + event.ts + ', no slice named ' + name +
375 ' with id=' + id + ' was open.');
376 continue;
377 }
378 var startAsyncEventState = startEventStatesByNameThenID[name][id];
379 delete startEventStatesByNameThenID[name][id];
380
381 // Create a slice from startAsyncEventState to asyncEventState
382 var slice = new tracing.TimelineAsyncSlice(
383 name,
384 tracing.getStringColorId(name),
385 startAsyncEventState.event.ts / 1000);
386
387 slice.duration =
388 (event.ts / 1000) - (startAsyncEventState.event.ts / 1000);
389
390 slice.startThread = startAsyncEventState.thread;
391 slice.endThread = asyncEventState.thread;
392 slice.id = id;
393 if (startAsyncEventState.event.args)
394 slice.args = startAsyncEventState.event.args;
395 else
396 slice.args = {};
397
398 // Add it to the start-thread's asyncSlices.
399 slice.startThread.asyncSlices.push(slice);
400 }
401 }
318 } 402 }
319 }; 403 };
320 404
321 tracing.TimelineModel.registerImporter(TraceEventImporter); 405 tracing.TimelineModel.registerImporter(TraceEventImporter);
322 406
323 return { 407 return {
324 TraceEventImporter: TraceEventImporter 408 TraceEventImporter: TraceEventImporter
325 }; 409 };
326 }); 410 });
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698