| OLD | NEW |
| (Empty) |
| 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 | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 /** | |
| 6 * @fileoverview TraceEventImporter imports TraceEvent-formatted data | |
| 7 * into the provided timeline model. | |
| 8 */ | |
| 9 cr.define('tracing', function() { | |
| 10 function ThreadState(tid) { | |
| 11 this.openSlices = []; | |
| 12 } | |
| 13 | |
| 14 function TraceEventImporter(model, eventData) { | |
| 15 this.model_ = model; | |
| 16 | |
| 17 if (typeof(eventData) === 'string' || eventData instanceof String) { | |
| 18 // If the event data begins with a [, then we know it should end with a ]. | |
| 19 // The reason we check for this is because some tracing implementations | |
| 20 // cannot guarantee that a ']' gets written to the trace file. So, we are | |
| 21 // forgiving and if this is obviously the case, we fix it up before | |
| 22 // throwing the string at JSON.parse. | |
| 23 if (eventData[0] == '[') { | |
| 24 n = eventData.length; | |
| 25 if (eventData[n - 1] != ']' && eventData[n - 1] != '\n') { | |
| 26 eventData = eventData + ']'; | |
| 27 } else if (eventData[n - 2] != ']' && eventData[n - 1] == '\n') { | |
| 28 eventData = eventData + ']'; | |
| 29 } else if (eventData[n - 3] != ']' && eventData[n - 2] == '\r' && | |
| 30 eventData[n - 1] == '\n') { | |
| 31 eventData = eventData + ']'; | |
| 32 } | |
| 33 } | |
| 34 this.events_ = JSON.parse(eventData); | |
| 35 | |
| 36 } else { | |
| 37 this.events_ = eventData; | |
| 38 } | |
| 39 | |
| 40 // Some trace_event implementations put the actual trace events | |
| 41 // inside a container. E.g { ... , traceEvents: [ ] } | |
| 42 // | |
| 43 // If we see that, just pull out the trace events. | |
| 44 if (this.events_.traceEvents) | |
| 45 this.events_ = this.events_.traceEvents; | |
| 46 | |
| 47 // To allow simple indexing of threads, we store all the threads by a | |
| 48 // PTID. A ptid is a pid and tid joined together x:y fashion, eg | |
| 49 // 1024:130. The ptid is a unique key for a thread in the trace. | |
| 50 this.threadStateByPTID_ = {}; | |
| 51 | |
| 52 // Async events need to be processed durign finalizeEvents | |
| 53 this.allAsyncEvents_ = []; | |
| 54 } | |
| 55 | |
| 56 /** | |
| 57 * @return {boolean} Whether obj is a TraceEvent array. | |
| 58 */ | |
| 59 TraceEventImporter.canImport = function(eventData) { | |
| 60 // May be encoded JSON. But we dont want to parse it fully yet. | |
| 61 // Use a simple heuristic: | |
| 62 // - eventData that starts with [ are probably trace_event | |
| 63 // - eventData that starts with { are probably trace_event | |
| 64 // May be encoded JSON. Treat files that start with { as importable by us. | |
| 65 if (typeof(eventData) === 'string' || eventData instanceof String) { | |
| 66 return eventData[0] == '{' || eventData[0] == '['; | |
| 67 } | |
| 68 | |
| 69 // Might just be an array of events | |
| 70 if (eventData instanceof Array && eventData.length && eventData[0].ph) | |
| 71 return true; | |
| 72 | |
| 73 // Might be an object with a traceEvents field in it. | |
| 74 if (eventData.traceEvents) | |
| 75 return eventData.traceEvents instanceof Array && | |
| 76 eventData.traceEvents[0].ph; | |
| 77 | |
| 78 return false; | |
| 79 }; | |
| 80 | |
| 81 TraceEventImporter.prototype = { | |
| 82 | |
| 83 __proto__: Object.prototype, | |
| 84 | |
| 85 /** | |
| 86 * Helper to process a 'begin' event (e.g. initiate a slice). | |
| 87 * @param {ThreadState} state Thread state (holds slices). | |
| 88 * @param {Object} event The current trace event. | |
| 89 */ | |
| 90 processBeginEvent: function(index, state, event) { | |
| 91 var colorId = tracing.getStringColorId(event.name); | |
| 92 var slice = | |
| 93 { index: index, | |
| 94 slice: new tracing.TimelineThreadSlice(event.name, colorId, | |
| 95 event.ts / 1000, | |
| 96 event.args) }; | |
| 97 | |
| 98 if (event.uts) | |
| 99 slice.slice.startInUserTime = event.uts / 1000; | |
| 100 | |
| 101 if (event.args['ui-nest'] === '0') { | |
| 102 this.model_.importErrors.push('ui-nest no longer supported.'); | |
| 103 return; | |
| 104 } | |
| 105 | |
| 106 state.openSlices.push(slice); | |
| 107 }, | |
| 108 | |
| 109 /** | |
| 110 * Helper to process an 'end' event (e.g. close a slice). | |
| 111 * @param {ThreadState} state Thread state (holds slices). | |
| 112 * @param {Object} event The current trace event. | |
| 113 */ | |
| 114 processEndEvent: function(state, event) { | |
| 115 if (event.args['ui-nest'] === '0') { | |
| 116 this.model_.importErrors.push('ui-nest no longer supported.'); | |
| 117 return; | |
| 118 } | |
| 119 if (state.openSlices.length == 0) { | |
| 120 // Ignore E events that are unmatched. | |
| 121 return; | |
| 122 } | |
| 123 var slice = state.openSlices.pop().slice; | |
| 124 slice.duration = (event.ts / 1000) - slice.start; | |
| 125 if (event.uts) | |
| 126 slice.durationInUserTime = (event.uts / 1000) - slice.startInUserTime; | |
| 127 for (var arg in event.args) | |
| 128 slice.args[arg] = event.args[arg]; | |
| 129 | |
| 130 // Store the slice on the correct subrow. | |
| 131 var thread = this.model_.getOrCreateProcess(event.pid). | |
| 132 getOrCreateThread(event.tid); | |
| 133 var subRowIndex = state.openSlices.length; | |
| 134 thread.getSubrow(subRowIndex).push(slice); | |
| 135 | |
| 136 // Add the slice to the subSlices array of its parent. | |
| 137 if (state.openSlices.length) { | |
| 138 var parentSlice = state.openSlices[state.openSlices.length - 1]; | |
| 139 parentSlice.slice.subSlices.push(slice); | |
| 140 } | |
| 141 }, | |
| 142 | |
| 143 /** | |
| 144 * Helper to process an 'async finish' event, which will close an open slice | |
| 145 * on a TimelineAsyncSliceGroup object. | |
| 146 **/ | |
| 147 processAsyncEvent: function(index, state, event) { | |
| 148 var thread = this.model_.getOrCreateProcess(event.pid). | |
| 149 getOrCreateThread(event.tid); | |
| 150 this.allAsyncEvents_.push({ | |
| 151 event: event, | |
| 152 thread: thread}); | |
| 153 }, | |
| 154 | |
| 155 /** | |
| 156 * Helper function that closes any open slices. This happens when a trace | |
| 157 * ends before an 'E' phase event can get posted. When that happens, this | |
| 158 * closes the slice at the highest timestamp we recorded and sets the | |
| 159 * didNotFinish flag to true. | |
| 160 */ | |
| 161 autoCloseOpenSlices: function() { | |
| 162 // We need to know the model bounds in order to assign an end-time to | |
| 163 // the open slices. | |
| 164 this.model_.updateBounds(); | |
| 165 | |
| 166 // The model's max value in the trace is wrong at this point if there are | |
| 167 // un-closed events. To close those events, we need the true global max | |
| 168 // value. To compute this, build a list of timestamps that weren't | |
| 169 // included in the max calculation, then compute the real maximum based on | |
| 170 // that. | |
| 171 var openTimestamps = []; | |
| 172 for (var ptid in this.threadStateByPTID_) { | |
| 173 var state = this.threadStateByPTID_[ptid]; | |
| 174 for (var i = 0; i < state.openSlices.length; i++) { | |
| 175 var slice = state.openSlices[i]; | |
| 176 openTimestamps.push(slice.slice.start); | |
| 177 for (var s = 0; s < slice.slice.subSlices.length; s++) { | |
| 178 var subSlice = slice.slice.subSlices[s]; | |
| 179 openTimestamps.push(subSlice.start); | |
| 180 if (subSlice.duration) | |
| 181 openTimestamps.push(subSlice.end); | |
| 182 } | |
| 183 } | |
| 184 } | |
| 185 | |
| 186 // Figure out the maximum value of model.maxTimestamp and | |
| 187 // Math.max(openTimestamps). Made complicated by the fact that the model | |
| 188 // timestamps might be undefined. | |
| 189 var realMaxTimestamp; | |
| 190 if (this.model_.maxTimestamp) { | |
| 191 realMaxTimestamp = Math.max(this.model_.maxTimestamp, | |
| 192 Math.max.apply(Math, openTimestamps)); | |
| 193 } else { | |
| 194 realMaxTimestamp = Math.max.apply(Math, openTimestamps); | |
| 195 } | |
| 196 | |
| 197 // Automatically close any slices are still open. These occur in a number | |
| 198 // of reasonable situations, e.g. deadlock. This pass ensures the open | |
| 199 // slices make it into the final model. | |
| 200 for (var ptid in this.threadStateByPTID_) { | |
| 201 var state = this.threadStateByPTID_[ptid]; | |
| 202 while (state.openSlices.length > 0) { | |
| 203 var slice = state.openSlices.pop(); | |
| 204 slice.slice.duration = realMaxTimestamp - slice.slice.start; | |
| 205 slice.slice.didNotFinish = true; | |
| 206 var event = this.events_[slice.index]; | |
| 207 | |
| 208 // Store the slice on the correct subrow. | |
| 209 var thread = this.model_.getOrCreateProcess(event.pid) | |
| 210 .getOrCreateThread(event.tid); | |
| 211 var subRowIndex = state.openSlices.length; | |
| 212 thread.getSubrow(subRowIndex).push(slice.slice); | |
| 213 | |
| 214 // Add the slice to the subSlices array of its parent. | |
| 215 if (state.openSlices.length) { | |
| 216 var parentSlice = state.openSlices[state.openSlices.length - 1]; | |
| 217 parentSlice.slice.subSlices.push(slice.slice); | |
| 218 } | |
| 219 } | |
| 220 } | |
| 221 }, | |
| 222 | |
| 223 /** | |
| 224 * Helper that creates and adds samples to a TimelineCounter object based on | |
| 225 * 'C' phase events. | |
| 226 */ | |
| 227 processCounterEvent: function(event) { | |
| 228 var ctr_name; | |
| 229 if (event.id !== undefined) | |
| 230 ctr_name = event.name + '[' + event.id + ']'; | |
| 231 else | |
| 232 ctr_name = event.name; | |
| 233 | |
| 234 var ctr = this.model_.getOrCreateProcess(event.pid) | |
| 235 .getOrCreateCounter(event.cat, ctr_name); | |
| 236 // Initialize the counter's series fields if needed. | |
| 237 if (ctr.numSeries == 0) { | |
| 238 for (var seriesName in event.args) { | |
| 239 ctr.seriesNames.push(seriesName); | |
| 240 ctr.seriesColors.push( | |
| 241 tracing.getStringColorId(ctr.name + '.' + seriesName)); | |
| 242 } | |
| 243 if (ctr.numSeries == 0) { | |
| 244 this.model_.importErrors.push('Expected counter ' + event.name + | |
| 245 ' to have at least one argument to use as a value.'); | |
| 246 // Drop the counter. | |
| 247 delete ctr.parent.counters[ctr.name]; | |
| 248 return; | |
| 249 } | |
| 250 } | |
| 251 | |
| 252 // Add the sample values. | |
| 253 ctr.timestamps.push(event.ts / 1000); | |
| 254 for (var i = 0; i < ctr.numSeries; i++) { | |
| 255 var seriesName = ctr.seriesNames[i]; | |
| 256 if (event.args[seriesName] === undefined) { | |
| 257 ctr.samples.push(0); | |
| 258 continue; | |
| 259 } | |
| 260 ctr.samples.push(event.args[seriesName]); | |
| 261 } | |
| 262 }, | |
| 263 | |
| 264 /** | |
| 265 * Walks through the events_ list and outputs the structures discovered to | |
| 266 * model_. | |
| 267 */ | |
| 268 importEvents: function() { | |
| 269 // Walk through events | |
| 270 var events = this.events_; | |
| 271 // Some events cannot be handled until we have done a first pass over the | |
| 272 // data set. So, accumulate them into a temporary data structure. | |
| 273 var second_pass_events = []; | |
| 274 for (var eI = 0; eI < events.length; eI++) { | |
| 275 var event = events[eI]; | |
| 276 var ptid = tracing.TimelineThread.getPTIDFromPidAndTid( | |
| 277 event.pid, event.tid); | |
| 278 | |
| 279 if (!(ptid in this.threadStateByPTID_)) | |
| 280 this.threadStateByPTID_[ptid] = new ThreadState(); | |
| 281 var state = this.threadStateByPTID_[ptid]; | |
| 282 | |
| 283 if (event.ph == 'B') { | |
| 284 this.processBeginEvent(eI, state, event); | |
| 285 } else if (event.ph == 'E') { | |
| 286 this.processEndEvent(state, event); | |
| 287 } else if (event.ph == 'S') { | |
| 288 this.processAsyncEvent(eI, state, event); | |
| 289 } else if (event.ph == 'F') { | |
| 290 this.processAsyncEvent(eI, state, event); | |
| 291 } else if (event.ph == 'T') { | |
| 292 this.processAsyncEvent(eI, 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 this.processBeginEvent(eI, state, event); | |
| 297 this.processEndEvent(state, event); | |
| 298 } else if (event.ph == 'C') { | |
| 299 this.processCounterEvent(event); | |
| 300 } else if (event.ph == 'M') { | |
| 301 if (event.name == 'thread_name') { | |
| 302 var thread = this.model_.getOrCreateProcess(event.pid) | |
| 303 .getOrCreateThread(event.tid); | |
| 304 thread.name = event.args.name; | |
| 305 } else { | |
| 306 this.model_.importErrors.push( | |
| 307 'Unrecognized metadata name: ' + event.name); | |
| 308 } | |
| 309 } else { | |
| 310 this.model_.importErrors.push( | |
| 311 'Unrecognized event phase: ' + event.ph + | |
| 312 '(' + event.name + ')'); | |
| 313 } | |
| 314 } | |
| 315 | |
| 316 // Autoclose any open slices. | |
| 317 var hasOpenSlices = false; | |
| 318 for (var ptid in this.threadStateByPTID_) { | |
| 319 var state = this.threadStateByPTID_[ptid]; | |
| 320 hasOpenSlices |= state.openSlices.length > 0; | |
| 321 } | |
| 322 if (hasOpenSlices) | |
| 323 this.autoCloseOpenSlices(); | |
| 324 }, | |
| 325 | |
| 326 /** | |
| 327 * Called by the TimelineModel after all other importers have imported their | |
| 328 * events. This function creates async slices for any async events we saw. | |
| 329 */ | |
| 330 finalizeImport: function() { | |
| 331 if (this.allAsyncEvents_.length == 0) | |
| 332 return; | |
| 333 | |
| 334 this.allAsyncEvents_.sort(function(x, y) { | |
| 335 return x.event.ts - y.event.ts; | |
| 336 }); | |
| 337 | |
| 338 var asyncEventStatesByNameThenID = {}; | |
| 339 | |
| 340 var allAsyncEvents = this.allAsyncEvents_; | |
| 341 for (var i = 0; i < allAsyncEvents.length; i++) { | |
| 342 var asyncEventState = allAsyncEvents[i]; | |
| 343 | |
| 344 var event = asyncEventState.event; | |
| 345 var name = event.name; | |
| 346 if (name === undefined) { | |
| 347 this.model_.importErrors.push( | |
| 348 'Async events (ph: S, T or F) require an name parameter.'); | |
| 349 continue; | |
| 350 } | |
| 351 | |
| 352 var id = event.id; | |
| 353 if (id === undefined) { | |
| 354 this.model_.importErrors.push( | |
| 355 'Async events (ph: S, T or F) require an id parameter.'); | |
| 356 continue; | |
| 357 } | |
| 358 | |
| 359 // TODO(simonjam): Add a synchronous tick on the appropriate thread. | |
| 360 | |
| 361 if (event.ph == 'S') { | |
| 362 if (asyncEventStatesByNameThenID[name] === undefined) | |
| 363 asyncEventStatesByNameThenID[name] = {}; | |
| 364 if (asyncEventStatesByNameThenID[name][id]) { | |
| 365 this.model_.importErrors.push( | |
| 366 'At ' + event.ts + ', an slice of the same id ' + id + | |
| 367 ' was alrady open.'); | |
| 368 continue; | |
| 369 } | |
| 370 asyncEventStatesByNameThenID[name][id] = []; | |
| 371 asyncEventStatesByNameThenID[name][id].push(asyncEventState); | |
| 372 } else { | |
| 373 if (asyncEventStatesByNameThenID[name] === undefined) { | |
| 374 this.model_.importErrors.push( | |
| 375 'At ' + event.ts + ', no slice named ' + name + | |
| 376 ' was open.'); | |
| 377 continue; | |
| 378 } | |
| 379 if (asyncEventStatesByNameThenID[name][id] === undefined) { | |
| 380 this.model_.importErrors.push( | |
| 381 'At ' + event.ts + ', no slice named ' + name + | |
| 382 ' with id=' + id + ' was open.'); | |
| 383 continue; | |
| 384 } | |
| 385 var events = asyncEventStatesByNameThenID[name][id]; | |
| 386 events.push(asyncEventState); | |
| 387 | |
| 388 if (event.ph == 'F') { | |
| 389 // Create a slice from start to end. | |
| 390 var slice = new tracing.TimelineAsyncSlice( | |
| 391 name, | |
| 392 tracing.getStringColorId(name), | |
| 393 events[0].event.ts / 1000); | |
| 394 | |
| 395 slice.duration = (event.ts / 1000) - (events[0].event.ts / 1000); | |
| 396 | |
| 397 slice.startThread = events[0].thread; | |
| 398 slice.endThread = asyncEventState.thread; | |
| 399 slice.id = id; | |
| 400 slice.args = events[0].event.args; | |
| 401 slice.subSlices = []; | |
| 402 | |
| 403 // Create subSlices for each step. | |
| 404 for (var j = 1; j < events.length; ++j) { | |
| 405 var subName = name; | |
| 406 if (events[j - 1].event.ph == 'T') | |
| 407 subName = name + ':' + events[j - 1].event.args.step; | |
| 408 var subSlice = new tracing.TimelineAsyncSlice( | |
| 409 subName, | |
| 410 tracing.getStringColorId(name + j), | |
| 411 events[j - 1].event.ts / 1000); | |
| 412 | |
| 413 subSlice.duration = | |
| 414 (events[j].event.ts / 1000) - (events[j - 1].event.ts / 1000); | |
| 415 | |
| 416 subSlice.startThread = events[j - 1].thread; | |
| 417 subSlice.endThread = events[j].thread; | |
| 418 subSlice.id = id; | |
| 419 subSlice.args = events[j - 1].event.args; | |
| 420 | |
| 421 slice.subSlices.push(subSlice); | |
| 422 } | |
| 423 | |
| 424 // The args for the finish event go in the last subSlice. | |
| 425 var lastSlice = slice.subSlices[slice.subSlices.length - 1]; | |
| 426 for (var arg in event.args) | |
| 427 lastSlice.args[arg] = event.args[arg]; | |
| 428 | |
| 429 // Add |slice| to the start-thread's asyncSlices. | |
| 430 slice.startThread.asyncSlices.push(slice); | |
| 431 delete asyncEventStatesByNameThenID[name][id]; | |
| 432 } | |
| 433 } | |
| 434 } | |
| 435 } | |
| 436 }; | |
| 437 | |
| 438 tracing.TimelineModel.registerImporter(TraceEventImporter); | |
| 439 | |
| 440 return { | |
| 441 TraceEventImporter: TraceEventImporter | |
| 442 }; | |
| 443 }); | |
| OLD | NEW |