1 /* 2 * Copyright 2014 The Chromium Authors. All rights reserved. 3 * Use of this source code is governed by a BSD-style license that can be 4 * found in the LICENSE file. 5 */ 6 7 /** 8 * @constructor 9 * @extends {WebInspector.TargetAwareObject} 10 */ 11 WebInspector.TracingModel = function(target) 12 { 13 WebInspector.TargetAwareObject.call(this, target); 14 this.reset(); 15 this._active = false; 16 InspectorBackend.registerTracingDispatcher(new WebInspector.TracingDispatcher(this)); 17 } 18 19 WebInspector.TracingModel.Events = { 20 "BufferUsage": "BufferUsage" 21 } 22 23 /** @typedef {!{ 24 cat: string, 25 pid: number, 26 tid: number, 27 ts: number, 28 ph: string, 29 name: string, 30 args: !Object, 31 dur: number, 32 id: number, 33 s: string 34 }} 35 */ 36 WebInspector.TracingModel.EventPayload; 37 38 /** 39 * @enum {string} 40 */ 41 WebInspector.TracingModel.Phase = { 42 Begin: "B", 43 End: "E", 44 Complete: "X", 45 Instant: "i", 46 AsyncBegin: "S", 47 AsyncStepInto: "T", 48 AsyncStepPast: "p", 49 AsyncEnd: "F", 50 FlowBegin: "s", 51 FlowStep: "t", 52 FlowEnd: "f", 53 Metadata: "M", 54 Counter: "C", 55 Sample: "P", 56 CreateObject: "N", 57 SnapshotObject: "O", 58 DeleteObject: "D" 59 }; 60 61 WebInspector.TracingModel.MetadataEvent = { 62 ProcessSortIndex: "process_sort_index", 63 ProcessName: "process_name", 64 ThreadSortIndex: "thread_sort_index", 65 ThreadName: "thread_name" 66 } 67 68 WebInspector.TracingModel.DevToolsMetadataEventCategory = "disabled-by-default-devtools.timeline"; 69 70 WebInspector.TracingModel.FrameLifecycleEventCategory = "cc,devtools"; 71 72 WebInspector.TracingModel.DevToolsMetadataEvent = { 73 TracingStartedInPage: "TracingStartedInPage", 74 }; 75 76 WebInspector.TracingModel.prototype = { 77 /** 78 * @return {!Array.<!WebInspector.TracingModel.Event>} 79 */ 80 devtoolsMetadataEvents: function() 81 { 82 return this._devtoolsMetadataEvents; 83 }, 84 85 /** 86 * @param {string} categoryFilter 87 * @param {string} options 88 * @param {function(?string)=} callback 89 */ 90 start: function(categoryFilter, options, callback) 91 { 92 this.reset(); 93 var bufferUsageReportingIntervalMs = 500; 94 /** 95 * @param {?string} error 96 * @param {string} sessionId 97 * @this {WebInspector.TracingModel} 98 */ 99 function callbackWrapper(error, sessionId) 100 { 101 this._sessionId = sessionId; 102 if (callback) 103 callback(error); 104 } 105 TracingAgent.start(categoryFilter, options, bufferUsageReportingIntervalMs, callbackWrapper.bind(this)); 106 this._active = true; 107 }, 108 109 /** 110 * @param {function()} callback 111 */ 112 stop: function(callback) 113 { 114 if (!this._active) { 115 callback(); 116 return; 117 } 118 this._pendingStopCallback = callback; 119 TracingAgent.end(); 120 }, 121 122 /** 123 * @return {?string} 124 */ 125 sessionId: function() 126 { 127 return this._sessionId; 128 }, 129 130 /** 131 * @param {string} sessionId 132 * @param {!Array.<!WebInspector.TracingModel.EventPayload>} events 133 */ 134 setEventsForTest: function(sessionId, events) 135 { 136 this.reset(); 137 this._sessionId = sessionId; 138 this._eventsCollected(events); 139 this._tracingComplete(); 140 }, 141 142 /** 143 * @param {number} usage 144 */ 145 _bufferUsage: function(usage) 146 { 147 this.dispatchEventToListeners(WebInspector.TracingModel.Events.BufferUsage, usage); 148 }, 149 150 /** 151 * @param {!Array.<!WebInspector.TracingModel.EventPayload>} events 152 */ 153 _eventsCollected: function(events) 154 { 155 for (var i = 0; i < events.length; ++i) 156 this._addEvent(events[i]); 157 }, 158 159 _tracingComplete: function() 160 { 161 this._active = false; 162 if (!this._pendingStopCallback) 163 return; 164 this._pendingStopCallback(); 165 this._pendingStopCallback = null; 166 }, 167 168 reset: function() 169 { 170 this._processById = {}; 171 this._minimumRecordTime = 0; 172 this._maximumRecordTime = 0; 173 this._sessionId = null; 174 this._devtoolsMetadataEvents = []; 175 }, 176 177 /** 178 * @param {!WebInspector.TracingModel.EventPayload} payload 179 */ 180 _addEvent: function(payload) 181 { 182 var process = this._processById[payload.pid]; 183 if (!process) { 184 process = new WebInspector.TracingModel.Process(payload.pid); 185 this._processById[payload.pid] = process; 186 } 187 var thread = process.threadById(payload.tid); 188 if (payload.ph !== WebInspector.TracingModel.Phase.Metadata) { 189 var timestamp = payload.ts / 1000; 190 // We do allow records for unrelated threads to arrive out-of-order, 191 // so there's a chance we're getting records from the past. 192 if (timestamp && (!this._minimumRecordTime || timestamp < this._minimumRecordTime)) 193 this._minimumRecordTime = timestamp; 194 if (!this._maximumRecordTime || timestamp > this._maximumRecordTime) 195 this._maximumRecordTime = timestamp; 196 var event = thread.addEvent(payload); 197 if (payload.ph === WebInspector.TracingModel.Phase.SnapshotObject) 198 process.addObject(event); 199 if (event && event.name === WebInspector.TracingModel.DevToolsMetadataEvent.TracingStartedInPage && 200 event.category === WebInspector.TracingModel.DevToolsMetadataEventCategory && 201 event.args["sessionId"] === this._sessionId) 202 this._devtoolsMetadataEvents.push(event); 203 return; 204 } 205 switch (payload.name) { 206 case WebInspector.TracingModel.MetadataEvent.ProcessSortIndex: 207 process._setSortIndex(payload.args["sort_index"]); 208 break; 209 case WebInspector.TracingModel.MetadataEvent.ProcessName: 210 process._setName(payload.args["name"]); 211 break; 212 case WebInspector.TracingModel.MetadataEvent.ThreadSortIndex: 213 thread._setSortIndex(payload.args["sort_index"]); 214 break; 215 case WebInspector.TracingModel.MetadataEvent.ThreadName: 216 thread._setName(payload.args["name"]); 217 break; 218 } 219 }, 220 221 /** 222 * @return {number} 223 */ 224 minimumRecordTime: function() 225 { 226 return this._minimumRecordTime; 227 }, 228 229 /** 230 * @return {number} 231 */ 232 maximumRecordTime: function() 233 { 234 return this._maximumRecordTime; 235 }, 236 237 /** 238 * @return {!Array.<!WebInspector.TracingModel.Process>} 239 */ 240 sortedProcesses: function() 241 { 242 return WebInspector.TracingModel.NamedObject._sort(Object.values(this._processById)); 243 }, 244 245 __proto__: WebInspector.TargetAwareObject.prototype 246 } 247 248 /** 249 * @constructor 250 * @param {!WebInspector.TracingModel.EventPayload} payload 251 * @param {number} level 252 * @param {?WebInspector.TracingModel.Thread} thread 253 */ 254 WebInspector.TracingModel.Event = function(payload, level, thread) 255 { 256 this.name = payload.name; 257 this.category = payload.cat; 258 this.startTime = payload.ts / 1000; 259 this.args = payload.args; 260 this.phase = payload.ph; 261 this.level = level; 262 263 if (payload.dur) 264 this._setEndTime((payload.ts + payload.dur) / 1000); 265 266 if (payload.id) 267 this.id = payload.id; 268 269 this.thread = thread; 270 271 /** @type {?string} */ 272 this.warning = null; 273 /** @type {?WebInspector.TracingModel.Event} */ 274 this.initiator = null; 275 /** @type {?Array.<!ConsoleAgent.CallFrame>} */ 276 this.stackTrace = null; 277 /** @type {?Element} */ 278 this.previewElement = null; 279 /** @type {?string} */ 280 this.imageURL = null; 281 /** @type {number} */ 282 this.backendNodeId = 0; 283 284 /** @type {number} */ 285 this.selfTime = 0; 286 } 287 288 WebInspector.TracingModel.Event.prototype = { 289 /** 290 * @param {number} endTime 291 */ 292 _setEndTime: function(endTime) 293 { 294 if (endTime < this.startTime) { 295 console.assert(false, "Event out of order: " + this.name); 296 return; 297 } 298 this.endTime = endTime; 299 this.duration = endTime - this.startTime; 300 }, 301 302 /** 303 * @param {!WebInspector.TracingModel.EventPayload} payload 304 */ 305 _complete: function(payload) 306 { 307 if (this.name !== payload.name) { 308 console.assert(false, "Open/close event mismatch: " + this.name + " vs. " + payload.name); 309 return; 310 } 311 if (payload.args) { 312 for (var name in payload.args) { 313 if (name in this.args) 314 console.error("Same argument name (" + name + ") is used for begin and end phases of " + this.name); 315 this.args[name] = payload.args[name]; 316 } 317 } 318 this._setEndTime(payload.ts / 1000); 319 } 320 } 321 322 /** 323 * @param {!WebInspector.TracingModel.Event} a 324 * @param {!WebInspector.TracingModel.Event} b 325 * @return {number} 326 */ 327 WebInspector.TracingModel.Event.compareStartTime = function (a, b) 328 { 329 return a.startTime - b.startTime; 330 } 331 332 /** 333 * @constructor 334 */ 335 WebInspector.TracingModel.NamedObject = function() 336 { 337 } 338 339 WebInspector.TracingModel.NamedObject.prototype = 340 { 341 /** 342 * @param {string} name 343 */ 344 _setName: function(name) 345 { 346 this._name = name; 347 }, 348 349 /** 350 * @return {string} 351 */ 352 name: function() 353 { 354 return this._name; 355 }, 356 357 /** 358 * @param {number} sortIndex 359 */ 360 _setSortIndex: function(sortIndex) 361 { 362 this._sortIndex = sortIndex; 363 }, 364 } 365 366 /** 367 * @param {!Array.<!WebInspector.TracingModel.NamedObject>} array 368 */ 369 WebInspector.TracingModel.NamedObject._sort = function(array) 370 { 371 /** 372 * @param {!WebInspector.TracingModel.NamedObject} a 373 * @param {!WebInspector.TracingModel.NamedObject} b 374 */ 375 function comparator(a, b) 376 { 377 return a._sortIndex !== b._sortIndex ? a._sortIndex - b._sortIndex : a.name().localeCompare(b.name()); 378 } 379 return array.sort(comparator); 380 } 381 382 /** 383 * @constructor 384 * @extends {WebInspector.TracingModel.NamedObject} 385 * @param {number} id 386 */ 387 WebInspector.TracingModel.Process = function(id) 388 { 389 WebInspector.TracingModel.NamedObject.call(this); 390 this._setName("Process " + id); 391 this._threads = {}; 392 this._objects = {}; 393 } 394 395 WebInspector.TracingModel.Process.prototype = { 396 /** 397 * @param {number} id 398 * @return {!WebInspector.TracingModel.Thread} 399 */ 400 threadById: function(id) 401 { 402 var thread = this._threads[id]; 403 if (!thread) { 404 thread = new WebInspector.TracingModel.Thread(this, id); 405 this._threads[id] = thread; 406 } 407 return thread; 408 }, 409 410 /** 411 * @param {!WebInspector.TracingModel.Event} event 412 */ 413 addObject: function(event) 414 { 415 this.objectsByName(event.name).push(event); 416 }, 417 418 /** 419 * @param {string} name 420 * @return {!Array.<!WebInspector.TracingModel.Event>} 421 */ 422 objectsByName: function(name) 423 { 424 var objects = this._objects[name]; 425 if (!objects) { 426 objects = []; 427 this._objects[name] = objects; 428 } 429 return objects; 430 }, 431 432 /** 433 * @return {!Array.<string>} 434 */ 435 sortedObjectNames: function() 436 { 437 return Object.keys(this._objects).sort(); 438 }, 439 440 /** 441 * @return {!Array.<!WebInspector.TracingModel.Thread>} 442 */ 443 sortedThreads: function() 444 { 445 return WebInspector.TracingModel.NamedObject._sort(Object.values(this._threads)); 446 }, 447 448 __proto__: WebInspector.TracingModel.NamedObject.prototype 449 } 450 451 /** 452 * @constructor 453 * @extends {WebInspector.TracingModel.NamedObject} 454 * @param {!WebInspector.TracingModel.Process} process 455 * @param {number} id 456 */ 457 WebInspector.TracingModel.Thread = function(process, id) 458 { 459 WebInspector.TracingModel.NamedObject.call(this); 460 this._process = process; 461 this._setName("Thread " + id); 462 this._events = []; 463 this._stack = []; 464 this._maxStackDepth = 0; 465 } 466 467 WebInspector.TracingModel.Thread.prototype = { 468 /** 469 * @param {!WebInspector.TracingModel.EventPayload} payload 470 * @return {?WebInspector.TracingModel.Event} event 471 */ 472 addEvent: function(payload) 473 { 474 for (var top = this._stack.peekLast(); top && top.endTime && top.endTime <= payload.ts / 1000;) { 475 this._stack.pop(); 476 top = this._stack.peekLast(); 477 } 478 if (payload.ph === WebInspector.TracingModel.Phase.End) { 479 var openEvent = this._stack.pop(); 480 // Quietly ignore unbalanced close events, they're legit (we could have missed start one). 481 if (openEvent) 482 openEvent._complete(payload); 483 return null; 484 } 485 486 var event = new WebInspector.TracingModel.Event(payload, this._stack.length, this); 487 if (payload.ph === WebInspector.TracingModel.Phase.Begin || payload.ph === WebInspector.TracingModel.Phase.Complete) { 488 this._stack.push(event); 489 if (this._maxStackDepth < this._stack.length) 490 this._maxStackDepth = this._stack.length; 491 } 492 if (this._events.length && this._events.peekLast().startTime > event.startTime) 493 console.assert(false, "Event is our of order: " + event.name); 494 this._events.push(event); 495 return event; 496 }, 497 498 /** 499 * @return {!WebInspector.TracingModel.Process} 500 */ 501 process: function() 502 { 503 return this._process; 504 }, 505 506 /** 507 * @return {!Array.<!WebInspector.TracingModel.Event>} 508 */ 509 events: function() 510 { 511 return this._events; 512 }, 513 514 /** 515 * @return {number} 516 */ 517 maxStackDepth: function() 518 { 519 // Reserve one for non-container events. 520 return this._maxStackDepth + 1; 521 }, 522 523 __proto__: WebInspector.TracingModel.NamedObject.prototype 524 } 525 526 527 /** 528 * @constructor 529 * @implements {TracingAgent.Dispatcher} 530 * @param {!WebInspector.TracingModel} tracingModel 531 */ 532 WebInspector.TracingDispatcher = function(tracingModel) 533 { 534 this._tracingModel = tracingModel; 535 } 536 537 WebInspector.TracingDispatcher.prototype = { 538 /** 539 * @param {number} usage 540 */ 541 bufferUsage: function(usage) 542 { 543 this._tracingModel._bufferUsage(usage); 544 }, 545 546 /** 547 * @param {!Array.<!WebInspector.TracingModel.EventPayload>} data 548 */ 549 dataCollected: function(data) 550 { 551 this._tracingModel._eventsCollected(data); 552 }, 553 554 tracingComplete: function() 555 { 556 this._tracingModel._tracingComplete(); 557 } 558 } 559