| 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 |