• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2013 Google Inc. All rights reserved.
3 * Copyright (C) 2012 Intel Inc. All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are
7 * met:
8 *
9 *     * Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 *     * Redistributions in binary form must reproduce the above
12 * copyright notice, this list of conditions and the following disclaimer
13 * in the documentation and/or other materials provided with the
14 * distribution.
15 *     * Neither the name of Google Inc. nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32/**
33 * @constructor
34 * @extends {WebInspector.Object}
35 * @param {!WebInspector.TimelineModel} model
36 * @param {!WebInspector.TimelineUIUtils} uiUtils
37 */
38WebInspector.TimelinePresentationModel = function(model, uiUtils)
39{
40    this._model = model;
41    this._uiUtils = uiUtils;
42    this._filters = [];
43    /**
44     * @type {!Map.<!WebInspector.TimelineModel.Record, !WebInspector.TimelinePresentationModel.Record>}
45     */
46    this._recordToPresentationRecord = new Map();
47    this.reset();
48}
49
50WebInspector.TimelinePresentationModel.prototype = {
51    /**
52     * @param {number} startTime
53     * @param {number} endTime
54     */
55    setWindowTimes: function(startTime, endTime)
56    {
57        this._windowStartTime = startTime;
58        this._windowEndTime = endTime;
59    },
60
61    /**
62     * @param {?WebInspector.TimelineModel.Record} record
63     * @return {?WebInspector.TimelinePresentationModel.Record}
64     */
65    toPresentationRecord: function(record)
66    {
67        return record ? this._recordToPresentationRecord.get(record) || null : null;
68    },
69
70    /**
71     * @return {!WebInspector.TimelinePresentationModel.Record}
72     */
73    rootRecord: function()
74    {
75        return this._rootRecord;
76    },
77
78    reset: function()
79    {
80        this._recordToPresentationRecord.clear();
81        this._rootRecord = new WebInspector.TimelinePresentationModel.RootRecord();
82        /** @type {!Object.<string, !WebInspector.TimelinePresentationModel.Record>} */
83        this._coalescingBuckets = {};
84    },
85
86    /**
87     * @param {!WebInspector.TimelineModel.Record} record
88     */
89    addRecord: function(record)
90    {
91        if (this._uiUtils.isProgram(record)) {
92            var records = record.children();
93            for (var i = 0; i < records.length; ++i)
94                this._innerAddRecord(this._rootRecord, records[i]);
95        } else {
96            this._innerAddRecord(this._rootRecord, record);
97        }
98    },
99
100    /**
101     * @param {!WebInspector.TimelinePresentationModel.Record} parentRecord
102     * @param {!WebInspector.TimelineModel.Record} record
103     */
104    _innerAddRecord: function(parentRecord, record)
105    {
106        var coalescingBucket;
107
108        // On main thread, only coalesce if the last event is of same type.
109        if (parentRecord === this._rootRecord)
110            coalescingBucket = record.thread() ? record.type() : "mainThread";
111        var coalescedRecord = this._findCoalescedParent(record, parentRecord, coalescingBucket);
112        if (coalescedRecord)
113            parentRecord = coalescedRecord;
114
115        var formattedRecord = new WebInspector.TimelinePresentationModel.ActualRecord(record, parentRecord);
116        this._recordToPresentationRecord.put(record, formattedRecord);
117
118        formattedRecord._collapsed = parentRecord === this._rootRecord;
119        if (coalescingBucket)
120            this._coalescingBuckets[coalescingBucket] = formattedRecord;
121
122        for (var i = 0; record.children() && i < record.children().length; ++i)
123            this._innerAddRecord(formattedRecord, record.children()[i]);
124
125        if (parentRecord.coalesced())
126            this._updateCoalescingParent(formattedRecord);
127    },
128
129    /**
130     * @param {!WebInspector.TimelineModel.Record} record
131     * @param {!WebInspector.TimelinePresentationModel.Record} newParent
132     * @param {string=} bucket
133     * @return {?WebInspector.TimelinePresentationModel.Record}
134     */
135    _findCoalescedParent: function(record, newParent, bucket)
136    {
137        const coalescingThresholdMillis = 5;
138
139        var lastRecord = bucket ? this._coalescingBuckets[bucket] : newParent._presentationChildren.peekLast();
140        if (lastRecord && lastRecord.coalesced())
141            lastRecord = lastRecord._presentationChildren.peekLast();
142        var startTime = record.startTime();
143        var endTime = record.endTime();
144        if (!lastRecord)
145            return null;
146        if (lastRecord.record().type() !== record.type())
147            return null;
148        if (!this._uiUtils.isCoalescable(record.type()))
149            return null;
150        if (lastRecord.record().endTime() + coalescingThresholdMillis < startTime)
151            return null;
152        if (endTime + coalescingThresholdMillis < lastRecord.record().startTime())
153            return null;
154        if (lastRecord.presentationParent().coalesced())
155            return lastRecord.presentationParent();
156        return this._replaceWithCoalescedRecord(lastRecord);
157    },
158
159    /**
160     * @param {!WebInspector.TimelinePresentationModel.Record} presentationRecord
161     * @return {!WebInspector.TimelinePresentationModel.Record}
162     */
163    _replaceWithCoalescedRecord: function(presentationRecord)
164    {
165        var record = presentationRecord.record();
166        var parent = presentationRecord._presentationParent;
167        var coalescedRecord = new WebInspector.TimelinePresentationModel.CoalescedRecord(record);
168
169        coalescedRecord._collapsed = true;
170        coalescedRecord._presentationChildren.push(presentationRecord);
171        presentationRecord._presentationParent = coalescedRecord;
172        if (presentationRecord.hasWarnings() || presentationRecord.childHasWarnings())
173            coalescedRecord._childHasWarnings = true;
174
175        coalescedRecord._presentationParent = parent;
176        parent._presentationChildren[parent._presentationChildren.indexOf(presentationRecord)] = coalescedRecord;
177        WebInspector.TimelineUIUtils.aggregateTimeByCategory(coalescedRecord.presentationAggregatedStats(), presentationRecord.presentationAggregatedStats());
178
179        return coalescedRecord;
180    },
181
182    /**
183     * @param {!WebInspector.TimelinePresentationModel.Record} presentationRecord
184     */
185    _updateCoalescingParent: function(presentationRecord)
186    {
187        var parentRecord = presentationRecord._presentationParent;
188        WebInspector.TimelineUIUtils.aggregateTimeByCategory(parentRecord.presentationAggregatedStats(), presentationRecord.presentationAggregatedStats());
189        if (parentRecord.endTime() < presentationRecord.endTime())
190            parentRecord._endTime = presentationRecord.endTime();
191    },
192
193    /**
194     * @param {?RegExp} textFilter
195     */
196    setTextFilter: function(textFilter)
197    {
198        this._textFilter = textFilter;
199    },
200
201    invalidateFilteredRecords: function()
202    {
203        delete this._filteredRecords;
204    },
205
206    /**
207     * @return {!Array.<!WebInspector.TimelinePresentationModel.Record>}
208     */
209    filteredRecords: function()
210    {
211        if (this._filteredRecords)
212            return this._filteredRecords;
213
214        var recordsInWindow = [];
215
216        var stack = [{children: this._rootRecord._presentationChildren, index: 0, parentIsCollapsed: false, parentRecord: {}}];
217        var revealedDepth = 0;
218
219        function revealRecordsInStack() {
220            for (var depth = revealedDepth + 1; depth < stack.length; ++depth) {
221                if (stack[depth - 1].parentIsCollapsed) {
222                    stack[depth].parentRecord._presentationParent._expandable = true;
223                    return;
224                }
225                stack[depth - 1].parentRecord._collapsed = false;
226                recordsInWindow.push(stack[depth].parentRecord);
227                stack[depth].windowLengthBeforeChildrenTraversal = recordsInWindow.length;
228                stack[depth].parentIsRevealed = true;
229                revealedDepth = depth;
230            }
231        }
232
233        while (stack.length) {
234            var entry = stack[stack.length - 1];
235            var records = entry.children;
236            if (records && entry.index < records.length) {
237                var record = records[entry.index];
238                ++entry.index;
239                if (record.startTime() < this._windowEndTime && record.endTime() > this._windowStartTime) {
240                    if (this._model.isVisible(record.record())) {
241                        record._presentationParent._expandable = true;
242                        if (this._textFilter)
243                            revealRecordsInStack();
244                        if (!entry.parentIsCollapsed) {
245                            recordsInWindow.push(record);
246                            revealedDepth = stack.length;
247                            entry.parentRecord._collapsed = false;
248                        }
249                    }
250                }
251
252                record._expandable = false;
253
254                stack.push({children: record._presentationChildren,
255                            index: 0,
256                            parentIsCollapsed: entry.parentIsCollapsed || (record._collapsed && (!this._textFilter || record._expandedOrCollapsedWhileFiltered)),
257                            parentRecord: record,
258                            windowLengthBeforeChildrenTraversal: recordsInWindow.length});
259            } else {
260                stack.pop();
261                revealedDepth = Math.min(revealedDepth, stack.length - 1);
262                entry.parentRecord._visibleChildrenCount = recordsInWindow.length - entry.windowLengthBeforeChildrenTraversal;
263            }
264        }
265
266        this._filteredRecords = recordsInWindow;
267        return recordsInWindow;
268    },
269
270    __proto__: WebInspector.Object.prototype
271}
272
273/**
274 * @constructor
275 * @param {?WebInspector.TimelinePresentationModel.Record} parentRecord
276 */
277WebInspector.TimelinePresentationModel.Record = function(parentRecord)
278{
279    /**
280     * @type {!Array.<!WebInspector.TimelinePresentationModel.Record>}
281     */
282    this._presentationChildren = [];
283
284    if (parentRecord) {
285        this._presentationParent = parentRecord;
286        parentRecord._presentationChildren.push(this);
287    }
288}
289
290WebInspector.TimelinePresentationModel.Record.prototype = {
291    /**
292     * @return {number}
293     */
294    startTime: function()
295    {
296        throw new Error("Not implemented.");
297    },
298
299    /**
300     * @return {number}
301     */
302    endTime: function()
303    {
304        throw new Error("Not implemented.");
305    },
306
307    /**
308     * @return {number}
309     */
310    selfTime: function()
311    {
312        throw new Error("Not implemented.");
313    },
314
315    /**
316     * @return {!WebInspector.TimelineModel.Record}
317     */
318    record: function()
319    {
320        throw new Error("Not implemented.");
321    },
322
323    /**
324     * @return {!Object.<string, number>}
325     */
326    presentationAggregatedStats: function()
327    {
328        throw new Error("Not implemented.");
329    },
330
331    /**
332     * @return {!Array.<!WebInspector.TimelinePresentationModel.Record>}
333     */
334    presentationChildren: function()
335    {
336        return this._presentationChildren;
337    },
338
339    /**
340     * @return {boolean}
341     */
342    coalesced: function()
343    {
344        return false;
345    },
346
347    /**
348     * @return {boolean}
349     */
350    collapsed: function()
351    {
352        return this._collapsed;
353    },
354
355    /**
356     * @param {boolean} collapsed
357     */
358    setCollapsed: function(collapsed)
359    {
360        this._collapsed = collapsed;
361        this._expandedOrCollapsedWhileFiltered = true;
362    },
363
364    /**
365     * @return {?WebInspector.TimelinePresentationModel.Record}
366     */
367    presentationParent: function()
368    {
369        return this._presentationParent || null;
370    },
371
372    /**
373     * @return {number}
374     */
375    visibleChildrenCount: function()
376    {
377        return this._visibleChildrenCount || 0;
378    },
379
380    /**
381     * @return {boolean}
382     */
383    expandable: function()
384    {
385        return !!this._expandable;
386    },
387
388    /**
389     * @return {boolean}
390     */
391    hasWarnings: function()
392    {
393        return false;
394    },
395
396    /**
397     * @return {boolean}
398     */
399    childHasWarnings: function()
400    {
401        return this._childHasWarnings;
402    },
403
404    /**
405     * @return {?WebInspector.TimelineRecordListRow}
406     */
407    listRow: function()
408    {
409        return this._listRow;
410    },
411
412    /**
413     * @param {!WebInspector.TimelineRecordListRow} listRow
414     */
415    setListRow: function(listRow)
416    {
417        this._listRow = listRow;
418    },
419
420    /**
421     * @return {?WebInspector.TimelineRecordGraphRow}
422     */
423    graphRow: function()
424    {
425        return this._graphRow;
426    },
427
428    /**
429     * @param {!WebInspector.TimelineRecordGraphRow} graphRow
430     */
431    setGraphRow: function(graphRow)
432    {
433        this._graphRow = graphRow;
434    }
435}
436
437/**
438 * @constructor
439 * @extends {WebInspector.TimelinePresentationModel.Record}
440 * @param {!WebInspector.TimelineModel.Record} record
441 * @param {?WebInspector.TimelinePresentationModel.Record} parentRecord
442 */
443WebInspector.TimelinePresentationModel.ActualRecord = function(record, parentRecord)
444{
445    WebInspector.TimelinePresentationModel.Record.call(this, parentRecord);
446    this._record = record;
447
448    if (this.hasWarnings()) {
449        for (var parent = this._presentationParent; parent && !parent._childHasWarnings; parent = parent._presentationParent)
450            parent._childHasWarnings = true;
451    }
452}
453
454WebInspector.TimelinePresentationModel.ActualRecord.prototype = {
455    /**
456     * @return {number}
457     */
458    startTime: function()
459    {
460        return this._record.startTime();
461    },
462
463    /**
464     * @return {number}
465     */
466    endTime: function()
467    {
468        return this._record.endTime();
469    },
470
471    /**
472     * @return {number}
473     */
474    selfTime: function()
475    {
476        return this._record.selfTime();
477    },
478
479    /**
480     * @return {!WebInspector.TimelineModel.Record}
481     */
482    record: function()
483    {
484        return this._record;
485    },
486
487    /**
488     * @return {!Object.<string, number>}
489     */
490    presentationAggregatedStats: function()
491    {
492        return this._record.aggregatedStats();
493    },
494
495    /**
496     * @return {boolean}
497     */
498    hasWarnings: function()
499    {
500        return !!this._record.warnings();
501    },
502
503    __proto__: WebInspector.TimelinePresentationModel.Record.prototype
504}
505
506/**
507 * @constructor
508 * @extends {WebInspector.TimelinePresentationModel.Record}
509 * @param {!WebInspector.TimelineModel.Record} record
510 */
511WebInspector.TimelinePresentationModel.CoalescedRecord = function(record)
512{
513    WebInspector.TimelinePresentationModel.Record.call(this, null);
514    this._startTime = record.startTime();
515    this._endTime = record.endTime();
516    this._aggregatedStats = {};
517}
518
519WebInspector.TimelinePresentationModel.CoalescedRecord.prototype = {
520    /**
521     * @return {number}
522     */
523    startTime: function()
524    {
525        return this._startTime;
526    },
527
528    /**
529     * @return {number}
530     */
531    endTime: function()
532    {
533        return this._endTime;
534    },
535
536    /**
537     * @return {number}
538     */
539    selfTime: function()
540    {
541        return 0;
542    },
543
544    /**
545     * @return {!WebInspector.TimelineModel.Record}
546     */
547    record: function()
548    {
549        return this._presentationChildren[0].record();
550    },
551
552    /**
553     * @return {!Object.<string, number>}
554     */
555    presentationAggregatedStats: function()
556    {
557        return this._aggregatedStats;
558    },
559
560    /**
561     * @return {boolean}
562     */
563    coalesced: function()
564    {
565        return true;
566    },
567
568    /**
569     * @return {boolean}
570     */
571    hasWarnings: function()
572    {
573        return false;
574    },
575
576    __proto__: WebInspector.TimelinePresentationModel.Record.prototype
577}
578
579/**
580 * @constructor
581 * @extends {WebInspector.TimelinePresentationModel.Record}
582 */
583WebInspector.TimelinePresentationModel.RootRecord = function()
584{
585    WebInspector.TimelinePresentationModel.Record.call(this, null);
586    this._aggregatedStats = {};
587}
588
589WebInspector.TimelinePresentationModel.RootRecord.prototype = {
590    /**
591     * @return {!Object.<string, number>}
592     */
593    presentationAggregatedStats: function()
594    {
595        return this._aggregatedStats;
596    },
597
598    /**
599     * @return {boolean}
600     */
601    hasWarnings: function()
602    {
603        return false;
604    },
605
606    __proto__: WebInspector.TimelinePresentationModel.Record.prototype
607}
608