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.Object} | |
10 */ | |
11 WebInspector.TracingModel = function() | |
12 { | |
13 WebInspector.Object.call(this); | |
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 } | |
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-d
evtools.timeline"; | |
69 | |
70 WebInspector.TracingModel.FrameLifecycleEventCategory = "cc,devtools"; | |
71 | |
72 WebInspector.TracingModel.DevToolsMetadataEvent = { | |
73 TracingStartedInPage: "TracingStartedInPage", | |
74 SetLayerTreeId: "SetLayerTreeId" | |
75 }; | |
76 | |
77 WebInspector.TracingModel.TraceEventName = { | |
78 ActivateLayerTree: "ActivateLayerTree", | |
79 BeginFrame: "BeginFrame", | |
80 BeginMainThreadFrame: "BeginMainThreadFrame", | |
81 CompositeLayers: "CompositeLayers", | |
82 DrawFrame: "DrawFrame", | |
83 PaintSetup: "PaintSetup", | |
84 RasterTask: "RasterTask", | |
85 RequestMainThreadFrame: "RequestMainThreadFrame" | |
86 }; | |
87 | |
88 WebInspector.TracingModel.prototype = { | |
89 /** | |
90 * @return {!Array.<!WebInspector.TracingModel.Event>} | |
91 */ | |
92 inspectedTargetMainThreadEvents: function() | |
93 { | |
94 return this._inspectedTargetMainThreadEvents; | |
95 }, | |
96 | |
97 /** | |
98 * @return {!Array.<!WebInspector.TracingModel.Event>} | |
99 */ | |
100 frameLifecycleEvents: function() | |
101 { | |
102 /** | |
103 * @param {!WebInspector.TracingModel.Event} a | |
104 * @param {!WebInspector.TracingModel.Event} b | |
105 */ | |
106 function compareStartTime(a, b) | |
107 { | |
108 return a.startTime - b.startTime; | |
109 } | |
110 return this._frameLifecycleEvents.sort(compareStartTime); | |
111 }, | |
112 | |
113 /** | |
114 * @param {string} categoryFilter | |
115 * @param {string} options | |
116 * @param {function(?string)=} callback | |
117 */ | |
118 start: function(categoryFilter, options, callback) | |
119 { | |
120 this.reset(); | |
121 var bufferUsageReportingIntervalMs = 500; | |
122 /** | |
123 * @param {?string} error | |
124 * @param {string} sessionId | |
125 * @this {WebInspector.TracingModel} | |
126 */ | |
127 function callbackWrapper(error, sessionId) | |
128 { | |
129 this._sessionId = sessionId; | |
130 if (callback) | |
131 callback(error); | |
132 } | |
133 TracingAgent.start(categoryFilter, options, bufferUsageReportingInterval
Ms, callbackWrapper.bind(this)); | |
134 this._active = true; | |
135 }, | |
136 | |
137 /** | |
138 * @param {function()} callback | |
139 */ | |
140 stop: function(callback) | |
141 { | |
142 if (!this._active) { | |
143 callback(); | |
144 return; | |
145 } | |
146 this._pendingStopCallback = callback; | |
147 TracingAgent.end(); | |
148 }, | |
149 | |
150 /** | |
151 * @return {?string} | |
152 */ | |
153 sessionId: function() | |
154 { | |
155 return this._sessionId; | |
156 }, | |
157 | |
158 /** | |
159 * @param {number} usage | |
160 */ | |
161 _bufferUsage: function(usage) | |
162 { | |
163 this.dispatchEventToListeners(WebInspector.TracingModel.Events.BufferUsa
ge, usage); | |
164 }, | |
165 | |
166 /** | |
167 * @param {!Array.<!WebInspector.TracingModel.EventPayload>} events | |
168 */ | |
169 _eventsCollected: function(events) | |
170 { | |
171 for (var i = 0; i < events.length; ++i) | |
172 this._addEvent(events[i]); | |
173 }, | |
174 | |
175 _tracingComplete: function() | |
176 { | |
177 this._bindings = new WebInspector.TracingModel.EventBindings(this); | |
178 this._active = false; | |
179 if (!this._pendingStopCallback) | |
180 return; | |
181 this._pendingStopCallback(); | |
182 this._pendingStopCallback = null; | |
183 }, | |
184 | |
185 /** | |
186 * @return {!WebInspector.TracingModel.EventBindings} | |
187 */ | |
188 bindings: function() | |
189 { | |
190 return this._bindings; | |
191 }, | |
192 | |
193 reset: function() | |
194 { | |
195 this._processById = {}; | |
196 this._minimumRecordTime = null; | |
197 this._maximumRecordTime = null; | |
198 this._sessionId = null; | |
199 this._inspectedTargetProcessId = null; | |
200 this._inspectedTargetMainThread = null; | |
201 this._inspectedTargetMainThreadEvents = []; | |
202 this._inspectedTargetLayerTreeHostId = 0; | |
203 this._frameLifecycleEvents = []; | |
204 this._bindings = null; | |
205 }, | |
206 | |
207 /** | |
208 * @param {!WebInspector.TracingModel.EventPayload} payload | |
209 */ | |
210 _addEvent: function(payload) | |
211 { | |
212 var process = this._processById[payload.pid]; | |
213 if (!process) { | |
214 process = new WebInspector.TracingModel.Process(payload.pid); | |
215 this._processById[payload.pid] = process; | |
216 } | |
217 if (payload.ph === WebInspector.TracingModel.Phase.SnapshotObject) { | |
218 process.addObject(payload); | |
219 return; | |
220 } | |
221 var thread = process.threadById(payload.tid); | |
222 if (payload.ph !== WebInspector.TracingModel.Phase.Metadata) { | |
223 var timestamp = payload.ts; | |
224 // We do allow records for unrelated threads to arrive out-of-order, | |
225 // so there's a chance we're getting records from the past. | |
226 if (timestamp && (!this._minimumRecordTime || timestamp < this._mini
mumRecordTime)) | |
227 this._minimumRecordTime = timestamp; | |
228 if (!this._maximumRecordTime || timestamp > this._maximumRecordTime) | |
229 this._maximumRecordTime = timestamp; | |
230 if (payload.cat === WebInspector.TracingModel.DevToolsMetadataEventC
ategory) | |
231 this._processDevToolsMetadataEvent(payload); | |
232 var event = thread.addEvent(payload); | |
233 if (!event) | |
234 return; | |
235 if (thread === this._inspectedTargetMainThread) | |
236 this._inspectedTargetMainThreadEvents.push(event); | |
237 if (payload.cat === WebInspector.TracingModel.FrameLifecycleEventCat
egory && payload.pid === this._inspectedTargetProcessId && | |
238 payload.args && payload.args["layerTreeId"] === this._inspectedT
argetLayerTreeId) { | |
239 this._frameLifecycleEvents.push(event); | |
240 } | |
241 return; | |
242 } | |
243 switch (payload.name) { | |
244 case WebInspector.TracingModel.MetadataEvent.ProcessSortIndex: | |
245 process._setSortIndex(payload.args["sort_index"]); | |
246 break; | |
247 case WebInspector.TracingModel.MetadataEvent.ProcessName: | |
248 process._setName(payload.args["name"]); | |
249 break; | |
250 case WebInspector.TracingModel.MetadataEvent.ThreadSortIndex: | |
251 thread._setSortIndex(payload.args["sort_index"]); | |
252 break; | |
253 case WebInspector.TracingModel.MetadataEvent.ThreadName: | |
254 thread._setName(payload.args["name"]); | |
255 break; | |
256 } | |
257 }, | |
258 | |
259 /** | |
260 * @param {!WebInspector.TracingModel.EventPayload} payload | |
261 */ | |
262 _processDevToolsMetadataEvent: function(payload) | |
263 { | |
264 if (payload.args["sessionId"] !== this._sessionId) | |
265 return; | |
266 if (payload.name === WebInspector.TracingModel.DevToolsMetadataEvent.Tra
cingStartedInPage) { | |
267 var thread = this._processById[payload.pid].threadById(payload.tid) | |
268 this._inspectedTargetProcessId = payload.pid; | |
269 this._inspectedTargetMainThread = thread; | |
270 this._inspectedTargetMainThreadEvents = this._inspectedTargetMainThr
eadEvents.concat(thread.events()); | |
271 } else if (payload.name === WebInspector.TracingModel.DevToolsMetadataEv
ent.SetLayerTreeId) { | |
272 this._inspectedTargetLayerTreeId = payload.args["layerTreeId"]; | |
273 } | |
274 }, | |
275 | |
276 /** | |
277 * @return {?number} | |
278 */ | |
279 minimumRecordTime: function() | |
280 { | |
281 return this._minimumRecordTime; | |
282 }, | |
283 | |
284 /** | |
285 * @return {?number} | |
286 */ | |
287 maximumRecordTime: function() | |
288 { | |
289 return this._maximumRecordTime; | |
290 }, | |
291 | |
292 /** | |
293 * @return {!Array.<!WebInspector.TracingModel.Process>} | |
294 */ | |
295 sortedProcesses: function() | |
296 { | |
297 return WebInspector.TracingModel.NamedObject._sort(Object.values(this._p
rocessById)); | |
298 }, | |
299 | |
300 __proto__: WebInspector.Object.prototype | |
301 } | |
302 | |
303 /** | |
304 * @param {!WebInspector.TracingModel} model | |
305 * @constructor | |
306 */ | |
307 WebInspector.TracingModel.EventBindings = function(model) | |
308 { | |
309 this._eventToWarning = new Map(); | |
310 this._model = model; | |
311 this._calculateWarnings(); | |
312 } | |
313 | |
314 WebInspector.TracingModel.EventBindings.prototype = { | |
315 /** | |
316 * @param {!WebInspector.TracingModel.Event} event | |
317 * @return {string|undefined} | |
318 */ | |
319 eventWarning: function(event) | |
320 { | |
321 return this._eventToWarning.get(event); | |
322 }, | |
323 | |
324 _calculateWarnings: function() | |
325 { | |
326 var events = this._model.inspectedTargetMainThreadEvents(); | |
327 var currentScriptEvent = null; | |
328 for (var i = 0, length = events.length; i < length; i++) { | |
329 var event = events[i]; | |
330 if (currentScriptEvent && event.startTime > currentScriptEvent.endTi
me) | |
331 currentScriptEvent = null; | |
332 if (event.name === WebInspector.TimelineModel.RecordType.Layout && c
urrentScriptEvent) | |
333 this._eventToWarning.put(event, WebInspector.UIString("Forced sy
nchronous layout is a possible performance bottleneck.")); | |
334 if (!currentScriptEvent && (event.name === WebInspector.TimelineMode
l.RecordType.EvaluateScript || event.name === WebInspector.TimelineModel.RecordT
ype.FunctionCall)) | |
335 currentScriptEvent = event; | |
336 } | |
337 } | |
338 } | |
339 | |
340 /** | |
341 * @constructor | |
342 * @param {!WebInspector.TracingModel.EventPayload} payload | |
343 * @param {number} level | |
344 */ | |
345 WebInspector.TracingModel.Event = function(payload, level) | |
346 { | |
347 this.name = payload.name; | |
348 this.category = payload.cat; | |
349 this.startTime = payload.ts; | |
350 this.args = payload.args; | |
351 this.phase = payload.ph; | |
352 this.level = level; | |
353 } | |
354 | |
355 WebInspector.TracingModel.Event.prototype = { | |
356 /** | |
357 * @param {number} duration | |
358 */ | |
359 _setDuration: function(duration) | |
360 { | |
361 this.endTime = this.startTime + duration; | |
362 this.duration = duration; | |
363 }, | |
364 | |
365 /** | |
366 * @param {!WebInspector.TracingModel.EventPayload} payload | |
367 */ | |
368 _complete: function(payload) | |
369 { | |
370 if (this.name !== payload.name) { | |
371 console.assert(false, "Open/close event mismatch: " + this.name + "
vs. " + payload.name); | |
372 return; | |
373 } | |
374 var duration = payload.ts - this.startTime; | |
375 if (duration < 0) { | |
376 console.assert(false, "Event out of order: " + this.name); | |
377 return; | |
378 } | |
379 this._setDuration(duration); | |
380 } | |
381 } | |
382 | |
383 /** | |
384 * @constructor | |
385 */ | |
386 WebInspector.TracingModel.NamedObject = function() | |
387 { | |
388 } | |
389 | |
390 WebInspector.TracingModel.NamedObject.prototype = | |
391 { | |
392 /** | |
393 * @param {string} name | |
394 */ | |
395 _setName: function(name) | |
396 { | |
397 this._name = name; | |
398 }, | |
399 | |
400 /** | |
401 * @return {string} | |
402 */ | |
403 name: function() | |
404 { | |
405 return this._name; | |
406 }, | |
407 | |
408 /** | |
409 * @param {number} sortIndex | |
410 */ | |
411 _setSortIndex: function(sortIndex) | |
412 { | |
413 this._sortIndex = sortIndex; | |
414 }, | |
415 } | |
416 | |
417 /** | |
418 * @param {!Array.<!WebInspector.TracingModel.NamedObject>} array | |
419 */ | |
420 WebInspector.TracingModel.NamedObject._sort = function(array) | |
421 { | |
422 /** | |
423 * @param {!WebInspector.TracingModel.NamedObject} a | |
424 * @param {!WebInspector.TracingModel.NamedObject} b | |
425 */ | |
426 function comparator(a, b) | |
427 { | |
428 return a._sortIndex !== b._sortIndex ? a._sortIndex - b._sortIndex : a.n
ame().localeCompare(b.name()); | |
429 } | |
430 return array.sort(comparator); | |
431 } | |
432 | |
433 /** | |
434 * @constructor | |
435 * @extends {WebInspector.TracingModel.NamedObject} | |
436 * @param {number} id | |
437 */ | |
438 WebInspector.TracingModel.Process = function(id) | |
439 { | |
440 WebInspector.TracingModel.NamedObject.call(this); | |
441 this._setName("Process " + id); | |
442 this._threads = {}; | |
443 this._objects = {}; | |
444 } | |
445 | |
446 WebInspector.TracingModel.Process.prototype = { | |
447 /** | |
448 * @param {number} id | |
449 * @return {!WebInspector.TracingModel.Thread} | |
450 */ | |
451 threadById: function(id) | |
452 { | |
453 var thread = this._threads[id]; | |
454 if (!thread) { | |
455 thread = new WebInspector.TracingModel.Thread(id); | |
456 this._threads[id] = thread; | |
457 } | |
458 return thread; | |
459 }, | |
460 | |
461 /** | |
462 * @param {!WebInspector.TracingModel.EventPayload} event | |
463 */ | |
464 addObject: function(event) | |
465 { | |
466 this.objectsByName(event.name).push(new WebInspector.TracingModel.Event(
event, 0)); | |
467 }, | |
468 | |
469 /** | |
470 * @param {string} name | |
471 * @return {!Array.<!WebInspector.TracingModel.Event>} | |
472 */ | |
473 objectsByName: function(name) | |
474 { | |
475 var objects = this._objects[name]; | |
476 if (!objects) { | |
477 objects = []; | |
478 this._objects[name] = objects; | |
479 } | |
480 return objects; | |
481 }, | |
482 | |
483 /** | |
484 * @return {!Array.<string>} | |
485 */ | |
486 sortedObjectNames: function() | |
487 { | |
488 return Object.keys(this._objects).sort(); | |
489 }, | |
490 | |
491 /** | |
492 * @return {!Array.<!WebInspector.TracingModel.Thread>} | |
493 */ | |
494 sortedThreads: function() | |
495 { | |
496 return WebInspector.TracingModel.NamedObject._sort(Object.values(this._t
hreads)); | |
497 }, | |
498 | |
499 __proto__: WebInspector.TracingModel.NamedObject.prototype | |
500 } | |
501 | |
502 /** | |
503 * @constructor | |
504 * @extends {WebInspector.TracingModel.NamedObject} | |
505 * @param {number} id | |
506 */ | |
507 WebInspector.TracingModel.Thread = function(id) | |
508 { | |
509 WebInspector.TracingModel.NamedObject.call(this); | |
510 this._setName("Thread " + id); | |
511 this._events = []; | |
512 this._stack = []; | |
513 this._maxStackDepth = 0; | |
514 } | |
515 | |
516 WebInspector.TracingModel.Thread.prototype = { | |
517 /** | |
518 * @param {!WebInspector.TracingModel.EventPayload} payload | |
519 * @return {?WebInspector.TracingModel.Event} event | |
520 */ | |
521 addEvent: function(payload) | |
522 { | |
523 for (var top = this._stack.peekLast(); top && top.endTime && top.endTime
<= payload.ts;) { | |
524 this._stack.pop(); | |
525 top = this._stack.peekLast(); | |
526 } | |
527 if (payload.ph === WebInspector.TracingModel.Phase.End) { | |
528 var openEvent = this._stack.pop(); | |
529 // Quietly ignore unbalanced close events, they're legit (we could h
ave missed start one). | |
530 if (openEvent) | |
531 openEvent._complete(payload); | |
532 return null; | |
533 } | |
534 | |
535 var event = new WebInspector.TracingModel.Event(payload, this._stack.len
gth); | |
536 if (payload.ph === WebInspector.TracingModel.Phase.Begin || payload.ph =
== WebInspector.TracingModel.Phase.Complete) { | |
537 if (payload.ph === WebInspector.TracingModel.Phase.Complete) | |
538 event._setDuration(payload.dur); | |
539 this._stack.push(event); | |
540 if (this._maxStackDepth < this._stack.length) | |
541 this._maxStackDepth = this._stack.length; | |
542 } | |
543 if (this._events.length && this._events.peekLast().startTime > event.sta
rtTime) | |
544 console.assert(false, "Event is our of order: " + event.name); | |
545 this._events.push(event); | |
546 return event; | |
547 }, | |
548 | |
549 /** | |
550 * @return {!Array.<!WebInspector.TracingModel.Event>} | |
551 */ | |
552 events: function() | |
553 { | |
554 return this._events; | |
555 }, | |
556 | |
557 /** | |
558 * @return {number} | |
559 */ | |
560 maxStackDepth: function() | |
561 { | |
562 // Reserve one for non-container events. | |
563 return this._maxStackDepth + 1; | |
564 }, | |
565 | |
566 __proto__: WebInspector.TracingModel.NamedObject.prototype | |
567 } | |
568 | |
569 | |
570 /** | |
571 * @constructor | |
572 * @implements {TracingAgent.Dispatcher} | |
573 * @param {!WebInspector.TracingModel} tracingModel | |
574 */ | |
575 WebInspector.TracingDispatcher = function(tracingModel) | |
576 { | |
577 this._tracingModel = tracingModel; | |
578 } | |
579 | |
580 WebInspector.TracingDispatcher.prototype = { | |
581 /** | |
582 * @param {number} usage | |
583 */ | |
584 bufferUsage: function(usage) | |
585 { | |
586 this._tracingModel._bufferUsage(usage); | |
587 }, | |
588 | |
589 /** | |
590 * @param {!Array.<!WebInspector.TracingModel.EventPayload>} data | |
591 */ | |
592 dataCollected: function(data) | |
593 { | |
594 this._tracingModel._eventsCollected(data); | |
595 }, | |
596 | |
597 tracingComplete: function() | |
598 { | |
599 this._tracingModel._tracingComplete(); | |
600 } | |
601 } | |
OLD | NEW |