OLD | NEW |
| (Empty) |
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.SDKObject} | |
10 */ | |
11 WebInspector.TracingModel = function(target) | |
12 { | |
13 WebInspector.SDKObject.call(this, target); | |
14 this.reset(); | |
15 this._active = false; | |
16 InspectorBackend.registerTracingDispatcher(new WebInspector.TracingDispatche
r(this)); | |
17 } | |
18 | |
19 WebInspector.TracingModel.Events = { | |
20 "BufferUsage": "BufferUsage", | |
21 "TracingStarted": "TracingStarted", | |
22 "TracingStopped": "TracingStopped", | |
23 "TracingComplete": "TracingComplete" | |
24 } | |
25 | |
26 /** @typedef {!{ | |
27 cat: string, | |
28 pid: number, | |
29 tid: number, | |
30 ts: number, | |
31 ph: string, | |
32 name: string, | |
33 args: !Object, | |
34 dur: number, | |
35 id: number, | |
36 s: string | |
37 }} | |
38 */ | |
39 WebInspector.TracingModel.EventPayload; | |
40 | |
41 /** | |
42 * @enum {string} | |
43 */ | |
44 WebInspector.TracingModel.Phase = { | |
45 Begin: "B", | |
46 End: "E", | |
47 Complete: "X", | |
48 Instant: "i", | |
49 AsyncBegin: "S", | |
50 AsyncStepInto: "T", | |
51 AsyncStepPast: "p", | |
52 AsyncEnd: "F", | |
53 FlowBegin: "s", | |
54 FlowStep: "t", | |
55 FlowEnd: "f", | |
56 Metadata: "M", | |
57 Counter: "C", | |
58 Sample: "P", | |
59 CreateObject: "N", | |
60 SnapshotObject: "O", | |
61 DeleteObject: "D" | |
62 }; | |
63 | |
64 WebInspector.TracingModel.MetadataEvent = { | |
65 ProcessSortIndex: "process_sort_index", | |
66 ProcessName: "process_name", | |
67 ThreadSortIndex: "thread_sort_index", | |
68 ThreadName: "thread_name" | |
69 } | |
70 | |
71 WebInspector.TracingModel.DevToolsMetadataEventCategory = "disabled-by-default-d
evtools.timeline"; | |
72 | |
73 WebInspector.TracingModel.FrameLifecycleEventCategory = "cc,devtools"; | |
74 | |
75 WebInspector.TracingModel.DevToolsMetadataEvent = { | |
76 TracingStartedInPage: "TracingStartedInPage", | |
77 TracingStartedInWorker: "TracingStartedInWorker", | |
78 }; | |
79 | |
80 WebInspector.TracingModel.prototype = { | |
81 /** | |
82 * @return {!Array.<!WebInspector.TracingModel.Event>} | |
83 */ | |
84 devtoolsMetadataEvents: function() | |
85 { | |
86 return this._devtoolsMetadataEvents; | |
87 }, | |
88 | |
89 /** | |
90 * @param {string} categoryFilter | |
91 * @param {string} options | |
92 * @param {function(?string)=} callback | |
93 */ | |
94 start: function(categoryFilter, options, callback) | |
95 { | |
96 this.target().profilingLock.acquire(); | |
97 this.reset(); | |
98 var bufferUsageReportingIntervalMs = 500; | |
99 TracingAgent.start(categoryFilter, options, bufferUsageReportingInterval
Ms, callback); | |
100 this._active = true; | |
101 }, | |
102 | |
103 stop: function() | |
104 { | |
105 if (!this._active) | |
106 return; | |
107 TracingAgent.end(this._onStop.bind(this)); | |
108 this.target().profilingLock.release(); | |
109 }, | |
110 | |
111 /** | |
112 * @return {?string} | |
113 */ | |
114 sessionId: function() | |
115 { | |
116 return this._sessionId; | |
117 }, | |
118 | |
119 /** | |
120 * @param {string} sessionId | |
121 * @param {!Array.<!WebInspector.TracingModel.EventPayload>} events | |
122 */ | |
123 setEventsForTest: function(sessionId, events) | |
124 { | |
125 this.reset(); | |
126 this._sessionId = sessionId; | |
127 this._eventsCollected(events); | |
128 }, | |
129 | |
130 /** | |
131 * @param {number} usage | |
132 */ | |
133 _bufferUsage: function(usage) | |
134 { | |
135 this.dispatchEventToListeners(WebInspector.TracingModel.Events.BufferUsa
ge, usage); | |
136 }, | |
137 | |
138 /** | |
139 * @param {!Array.<!WebInspector.TracingModel.EventPayload>} events | |
140 */ | |
141 _eventsCollected: function(events) | |
142 { | |
143 for (var i = 0; i < events.length; ++i) { | |
144 this._addEvent(events[i]); | |
145 this._rawEvents.push(events[i]); | |
146 } | |
147 }, | |
148 | |
149 _tracingComplete: function() | |
150 { | |
151 this._active = false; | |
152 this.dispatchEventToListeners(WebInspector.TracingModel.Events.TracingCo
mplete); | |
153 }, | |
154 | |
155 /** | |
156 * @param {string} sessionId | |
157 */ | |
158 _tracingStarted: function(sessionId) | |
159 { | |
160 this.reset(); | |
161 this._active = true; | |
162 this._sessionId = sessionId; | |
163 this.dispatchEventToListeners(WebInspector.TracingModel.Events.TracingSt
arted); | |
164 }, | |
165 | |
166 _onStop: function() | |
167 { | |
168 this.dispatchEventToListeners(WebInspector.TracingModel.Events.TracingSt
opped); | |
169 this._active = false; | |
170 }, | |
171 | |
172 reset: function() | |
173 { | |
174 this._processById = {}; | |
175 this._minimumRecordTime = 0; | |
176 this._maximumRecordTime = 0; | |
177 this._sessionId = null; | |
178 this._devtoolsMetadataEvents = []; | |
179 this._rawEvents = []; | |
180 }, | |
181 | |
182 /** | |
183 * @return {!Array.<!WebInspector.TracingModel.EventPayload>} | |
184 */ | |
185 rawEvents: function() | |
186 { | |
187 return this._rawEvents; | |
188 }, | |
189 | |
190 /** | |
191 * @param {!WebInspector.TracingModel.EventPayload} payload | |
192 */ | |
193 _addEvent: function(payload) | |
194 { | |
195 var process = this._processById[payload.pid]; | |
196 if (!process) { | |
197 process = new WebInspector.TracingModel.Process(payload.pid); | |
198 this._processById[payload.pid] = process; | |
199 } | |
200 var thread = process.threadById(payload.tid); | |
201 if (payload.ph !== WebInspector.TracingModel.Phase.Metadata) { | |
202 var timestamp = payload.ts / 1000; | |
203 // We do allow records for unrelated threads to arrive out-of-order, | |
204 // so there's a chance we're getting records from the past. | |
205 if (timestamp && (!this._minimumRecordTime || timestamp < this._mini
mumRecordTime)) | |
206 this._minimumRecordTime = timestamp; | |
207 if (!this._maximumRecordTime || timestamp > this._maximumRecordTime) | |
208 this._maximumRecordTime = timestamp; | |
209 var event = thread.addEvent(payload); | |
210 if (payload.ph === WebInspector.TracingModel.Phase.SnapshotObject) | |
211 process.addObject(event); | |
212 if (event && event.name === WebInspector.TracingModel.DevToolsMetada
taEvent.TracingStartedInPage && | |
213 event.category === WebInspector.TracingModel.DevToolsMetadataEve
ntCategory && | |
214 event.args["sessionId"] === this._sessionId) | |
215 this._devtoolsMetadataEvents.push(event); | |
216 return; | |
217 } | |
218 switch (payload.name) { | |
219 case WebInspector.TracingModel.MetadataEvent.ProcessSortIndex: | |
220 process._setSortIndex(payload.args["sort_index"]); | |
221 break; | |
222 case WebInspector.TracingModel.MetadataEvent.ProcessName: | |
223 process._setName(payload.args["name"]); | |
224 break; | |
225 case WebInspector.TracingModel.MetadataEvent.ThreadSortIndex: | |
226 thread._setSortIndex(payload.args["sort_index"]); | |
227 break; | |
228 case WebInspector.TracingModel.MetadataEvent.ThreadName: | |
229 thread._setName(payload.args["name"]); | |
230 break; | |
231 } | |
232 }, | |
233 | |
234 /** | |
235 * @return {number} | |
236 */ | |
237 minimumRecordTime: function() | |
238 { | |
239 return this._minimumRecordTime; | |
240 }, | |
241 | |
242 /** | |
243 * @return {number} | |
244 */ | |
245 maximumRecordTime: function() | |
246 { | |
247 return this._maximumRecordTime; | |
248 }, | |
249 | |
250 /** | |
251 * @return {!Array.<!WebInspector.TracingModel.Process>} | |
252 */ | |
253 sortedProcesses: function() | |
254 { | |
255 return WebInspector.TracingModel.NamedObject._sort(Object.values(this._p
rocessById)); | |
256 }, | |
257 | |
258 __proto__: WebInspector.SDKObject.prototype | |
259 } | |
260 | |
261 | |
262 /** | |
263 * @constructor | |
264 * @param {!WebInspector.TracingModel} tracingModel | |
265 */ | |
266 WebInspector.TracingModel.Loader = function(tracingModel) | |
267 { | |
268 this._tracingModel = tracingModel; | |
269 this._events = []; | |
270 this._sessionIdFound = false; | |
271 } | |
272 | |
273 WebInspector.TracingModel.Loader.prototype = { | |
274 /** | |
275 * @param {!Array.<!WebInspector.TracingModel.EventPayload>} events | |
276 */ | |
277 loadNextChunk: function(events) { | |
278 if (this._sessionIdFound) { | |
279 this._tracingModel._eventsCollected(events); | |
280 return; | |
281 } | |
282 | |
283 var sessionId = null; | |
284 for (var i = 0, length = events.length; i < length; i++) { | |
285 var event = events[i]; | |
286 this._events.push(event); | |
287 | |
288 if (event.name === WebInspector.TracingModel.DevToolsMetadataEvent.T
racingStartedInPage && | |
289 event.cat.indexOf(WebInspector.TracingModel.DevToolsMetadataEven
tCategory) !== -1 && | |
290 !this._sessionIdFound) { | |
291 sessionId = event.args["sessionId"]; | |
292 this._sessionIdFound = true; | |
293 } | |
294 } | |
295 | |
296 if (this._sessionIdFound) { | |
297 this._tracingModel._tracingStarted(sessionId); | |
298 this._tracingModel._eventsCollected(this._events); | |
299 } | |
300 }, | |
301 | |
302 finish: function() | |
303 { | |
304 if (this._sessionIdFound) | |
305 this._tracingModel._tracingComplete(); | |
306 else | |
307 WebInspector.console.error(WebInspector.UIString("Trace event %s not
found while loading tracing model.", WebInspector.TracingModel.DevToolsMetadata
Event.TracingStartedInPage)); | |
308 } | |
309 } | |
310 | |
311 | |
312 /** | |
313 * @constructor | |
314 * @param {!WebInspector.TracingModel.EventPayload} payload | |
315 * @param {number} level | |
316 * @param {?WebInspector.TracingModel.Thread} thread | |
317 */ | |
318 WebInspector.TracingModel.Event = function(payload, level, thread) | |
319 { | |
320 this.name = payload.name; | |
321 this.category = payload.cat; | |
322 this.startTime = payload.ts / 1000; | |
323 if (payload.args) { | |
324 // Create a new object to avoid modifying original payload which may be
saved to file. | |
325 this.args = {}; | |
326 for (var name in payload.args) | |
327 this.args[name] = payload.args[name]; | |
328 } | |
329 this.phase = payload.ph; | |
330 this.level = level; | |
331 | |
332 if (typeof payload.dur === "number") | |
333 this._setEndTime((payload.ts + payload.dur) / 1000); | |
334 | |
335 if (payload.id) | |
336 this.id = payload.id; | |
337 | |
338 this.thread = thread; | |
339 | |
340 /** @type {?string} */ | |
341 this.warning = null; | |
342 /** @type {?WebInspector.TracingModel.Event} */ | |
343 this.initiator = null; | |
344 /** @type {?Array.<!ConsoleAgent.CallFrame>} */ | |
345 this.stackTrace = null; | |
346 /** @type {?Element} */ | |
347 this.previewElement = null; | |
348 /** @type {?string} */ | |
349 this.imageURL = null; | |
350 /** @type {number} */ | |
351 this.backendNodeId = 0; | |
352 | |
353 /** @type {number} */ | |
354 this.selfTime = 0; | |
355 } | |
356 | |
357 WebInspector.TracingModel.Event.prototype = { | |
358 /** | |
359 * @param {number} endTime | |
360 */ | |
361 _setEndTime: function(endTime) | |
362 { | |
363 if (endTime < this.startTime) { | |
364 console.assert(false, "Event out of order: " + this.name); | |
365 return; | |
366 } | |
367 this.endTime = endTime; | |
368 this.duration = endTime - this.startTime; | |
369 }, | |
370 | |
371 /** | |
372 * @param {!WebInspector.TracingModel.EventPayload} payload | |
373 */ | |
374 _complete: function(payload) | |
375 { | |
376 if (this.name !== payload.name) { | |
377 console.assert(false, "Open/close event mismatch: " + this.name + "
vs. " + payload.name + " at " + (payload.ts / 1000)); | |
378 return; | |
379 } | |
380 if (payload.args) { | |
381 for (var name in payload.args) { | |
382 if (name in this.args) | |
383 console.error("Same argument name (" + name + ") is used fo
r begin and end phases of " + this.name); | |
384 this.args[name] = payload.args[name]; | |
385 } | |
386 } | |
387 this._setEndTime(payload.ts / 1000); | |
388 } | |
389 } | |
390 | |
391 /** | |
392 * @param {!WebInspector.TracingModel.Event} a | |
393 * @param {!WebInspector.TracingModel.Event} b | |
394 * @return {number} | |
395 */ | |
396 WebInspector.TracingModel.Event.compareStartTime = function (a, b) | |
397 { | |
398 return a.startTime - b.startTime; | |
399 } | |
400 | |
401 /** | |
402 * @param {!WebInspector.TracingModel.Event} a | |
403 * @param {!WebInspector.TracingModel.Event} b | |
404 * @return {number} | |
405 */ | |
406 WebInspector.TracingModel.Event.orderedCompareStartTime = function (a, b) | |
407 { | |
408 // Array.mergeOrdered coalesces objects if comparator returns 0. | |
409 // To change this behavior this comparator return -1 in the case events | |
410 // startTime's are equal, so both events got placed into the result array. | |
411 return a.startTime - b.startTime || -1; | |
412 } | |
413 | |
414 /** | |
415 * @constructor | |
416 */ | |
417 WebInspector.TracingModel.NamedObject = function() | |
418 { | |
419 } | |
420 | |
421 WebInspector.TracingModel.NamedObject.prototype = | |
422 { | |
423 /** | |
424 * @param {string} name | |
425 */ | |
426 _setName: function(name) | |
427 { | |
428 this._name = name; | |
429 }, | |
430 | |
431 /** | |
432 * @return {string} | |
433 */ | |
434 name: function() | |
435 { | |
436 return this._name; | |
437 }, | |
438 | |
439 /** | |
440 * @param {number} sortIndex | |
441 */ | |
442 _setSortIndex: function(sortIndex) | |
443 { | |
444 this._sortIndex = sortIndex; | |
445 }, | |
446 } | |
447 | |
448 /** | |
449 * @param {!Array.<!WebInspector.TracingModel.NamedObject>} array | |
450 */ | |
451 WebInspector.TracingModel.NamedObject._sort = function(array) | |
452 { | |
453 /** | |
454 * @param {!WebInspector.TracingModel.NamedObject} a | |
455 * @param {!WebInspector.TracingModel.NamedObject} b | |
456 */ | |
457 function comparator(a, b) | |
458 { | |
459 return a._sortIndex !== b._sortIndex ? a._sortIndex - b._sortIndex : a.n
ame().localeCompare(b.name()); | |
460 } | |
461 return array.sort(comparator); | |
462 } | |
463 | |
464 /** | |
465 * @constructor | |
466 * @extends {WebInspector.TracingModel.NamedObject} | |
467 * @param {number} id | |
468 */ | |
469 WebInspector.TracingModel.Process = function(id) | |
470 { | |
471 WebInspector.TracingModel.NamedObject.call(this); | |
472 this._setName("Process " + id); | |
473 this._threads = {}; | |
474 this._objects = {}; | |
475 } | |
476 | |
477 WebInspector.TracingModel.Process.prototype = { | |
478 /** | |
479 * @param {number} id | |
480 * @return {!WebInspector.TracingModel.Thread} | |
481 */ | |
482 threadById: function(id) | |
483 { | |
484 var thread = this._threads[id]; | |
485 if (!thread) { | |
486 thread = new WebInspector.TracingModel.Thread(this, id); | |
487 this._threads[id] = thread; | |
488 } | |
489 return thread; | |
490 }, | |
491 | |
492 /** | |
493 * @param {!WebInspector.TracingModel.Event} event | |
494 */ | |
495 addObject: function(event) | |
496 { | |
497 this.objectsByName(event.name).push(event); | |
498 }, | |
499 | |
500 /** | |
501 * @param {string} name | |
502 * @return {!Array.<!WebInspector.TracingModel.Event>} | |
503 */ | |
504 objectsByName: function(name) | |
505 { | |
506 var objects = this._objects[name]; | |
507 if (!objects) { | |
508 objects = []; | |
509 this._objects[name] = objects; | |
510 } | |
511 return objects; | |
512 }, | |
513 | |
514 /** | |
515 * @return {!Array.<string>} | |
516 */ | |
517 sortedObjectNames: function() | |
518 { | |
519 return Object.keys(this._objects).sort(); | |
520 }, | |
521 | |
522 /** | |
523 * @return {!Array.<!WebInspector.TracingModel.Thread>} | |
524 */ | |
525 sortedThreads: function() | |
526 { | |
527 return WebInspector.TracingModel.NamedObject._sort(Object.values(this._t
hreads)); | |
528 }, | |
529 | |
530 __proto__: WebInspector.TracingModel.NamedObject.prototype | |
531 } | |
532 | |
533 /** | |
534 * @constructor | |
535 * @extends {WebInspector.TracingModel.NamedObject} | |
536 * @param {!WebInspector.TracingModel.Process} process | |
537 * @param {number} id | |
538 */ | |
539 WebInspector.TracingModel.Thread = function(process, id) | |
540 { | |
541 WebInspector.TracingModel.NamedObject.call(this); | |
542 this._process = process; | |
543 this._setName("Thread " + id); | |
544 this._events = []; | |
545 this._stack = []; | |
546 this._maxStackDepth = 0; | |
547 } | |
548 | |
549 WebInspector.TracingModel.Thread.prototype = { | |
550 | |
551 /** | |
552 * @return {?WebInspector.Target} | |
553 */ | |
554 target: function() | |
555 { | |
556 //FIXME: correctly specify target | |
557 return WebInspector.targetManager.targets()[0]; | |
558 }, | |
559 | |
560 /** | |
561 * @param {!WebInspector.TracingModel.EventPayload} payload | |
562 * @return {?WebInspector.TracingModel.Event} event | |
563 */ | |
564 addEvent: function(payload) | |
565 { | |
566 var timestamp = payload.ts / 1000; | |
567 for (var top = this._stack.peekLast(); top;) { | |
568 // For B/E pairs, ignore time and look for top matching B event, | |
569 // otherwise, only pop event if it's definitely is in the past. | |
570 if (payload.ph === WebInspector.TracingModel.Phase.End) { | |
571 if (payload.name === top.name) { | |
572 top._complete(payload); | |
573 this._stack.pop(); | |
574 return null; | |
575 } | |
576 } else if (top.phase === WebInspector.TracingModel.Phase.Begin || (t
op.endTime && (top.endTime > timestamp))) { | |
577 break; | |
578 } | |
579 this._stack.pop(); | |
580 top = this._stack.peekLast(); | |
581 } | |
582 // Quietly ignore unbalanced close events, they're legit (we could have
missed start one). | |
583 if (payload.ph === WebInspector.TracingModel.Phase.End) | |
584 return null; | |
585 | |
586 var event = new WebInspector.TracingModel.Event(payload, this._stack.len
gth, this); | |
587 if (payload.ph === WebInspector.TracingModel.Phase.Begin || payload.ph =
== WebInspector.TracingModel.Phase.Complete) { | |
588 this._stack.push(event); | |
589 if (this._maxStackDepth < this._stack.length) | |
590 this._maxStackDepth = this._stack.length; | |
591 } | |
592 if (this._events.length && this._events.peekLast().startTime > event.sta
rtTime) | |
593 console.assert(false, "Event is our of order: " + event.name); | |
594 this._events.push(event); | |
595 return event; | |
596 }, | |
597 | |
598 /** | |
599 * @return {!WebInspector.TracingModel.Process} | |
600 */ | |
601 process: function() | |
602 { | |
603 return this._process; | |
604 }, | |
605 | |
606 /** | |
607 * @return {!Array.<!WebInspector.TracingModel.Event>} | |
608 */ | |
609 events: function() | |
610 { | |
611 return this._events; | |
612 }, | |
613 | |
614 /** | |
615 * @return {number} | |
616 */ | |
617 maxStackDepth: function() | |
618 { | |
619 // Reserve one for non-container events. | |
620 return this._maxStackDepth + 1; | |
621 }, | |
622 | |
623 __proto__: WebInspector.TracingModel.NamedObject.prototype | |
624 } | |
625 | |
626 | |
627 /** | |
628 * @constructor | |
629 * @implements {TracingAgent.Dispatcher} | |
630 * @param {!WebInspector.TracingModel} tracingModel | |
631 */ | |
632 WebInspector.TracingDispatcher = function(tracingModel) | |
633 { | |
634 this._tracingModel = tracingModel; | |
635 } | |
636 | |
637 WebInspector.TracingDispatcher.prototype = { | |
638 /** | |
639 * @param {number} usage | |
640 */ | |
641 bufferUsage: function(usage) | |
642 { | |
643 this._tracingModel._bufferUsage(usage); | |
644 }, | |
645 | |
646 /** | |
647 * @param {!Array.<!WebInspector.TracingModel.EventPayload>} data | |
648 */ | |
649 dataCollected: function(data) | |
650 { | |
651 this._tracingModel._eventsCollected(data); | |
652 }, | |
653 | |
654 tracingComplete: function() | |
655 { | |
656 this._tracingModel._tracingComplete(); | |
657 }, | |
658 | |
659 /** | |
660 * @param {boolean} consoleTimeline | |
661 * @param {string} sessionId | |
662 */ | |
663 started: function(consoleTimeline, sessionId) | |
664 { | |
665 this._tracingModel._tracingStarted(sessionId); | |
666 }, | |
667 | |
668 stopped: function() | |
669 { | |
670 this._tracingModel._onStop(); | |
671 } | |
672 } | |
OLD | NEW |