OLD | NEW |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 }); |
OLD | NEW |