Changeset 242104 in webkit


Ignore:
Timestamp:
Feb 26, 2019, 1:40:50 PM (7 years ago)
Author:
Joseph Pecoraro
Message:

Web Inspector: CPU Usage Timeline - Main Thread Indicator
https://bugs.webkit.org/show_bug.cgi?id=194972

Reviewed by Devin Rousso.

  • UserInterface/Main.html:
  • UserInterface/Base/Utilities.js:

(value):
The existing enclosingNode doesn't work for SVG because its names
are lowercase. Add a simplified version for the svg case.

  • UserInterface/Views/RangeChart.js: Added.

(WI.RangeChart):
(WI.RangeChart.prototype.get size):
(WI.RangeChart.prototype.set size):
(WI.RangeChart.prototype.addRange):
(WI.RangeChart.prototype.clear):
(WI.RangeChart.prototype.layout):
A new chart that draws rects for given ranges.

  • UserInterface/Models/Timeline.js:

(WI.Timeline.prototype.recordsOverlappingTimeRange):
Helper to specifically get records touching a range. Useful
for when we have a single pixel spanning (startTime -> endTime)
and we want to find records in that pixel.

  • UserInterface/Views/CPUTimelineView.css:

(.timeline-view.cpu .cpu-usage-indicator-view > .graph > .range-chart rect):
(.timeline-view.cpu .cpu-usage-indicator-view > .graph > .range-chart .sample-type-script):
(.timeline-view.cpu .cpu-usage-indicator-view > .graph > .range-chart .sample-type-style):
(.timeline-view.cpu .cpu-usage-indicator-view > .graph > .range-chart .sample-type-layout):
(.timeline-view.cpu .cpu-usage-indicator-view > .graph > .range-chart .sample-type-paint):

  • UserInterface/Views/CPUTimelineView.js:

(WI.CPUTimelineView.prototype.get indicatorViewHeight):
(WI.CPUTimelineView.prototype.clear):
(WI.CPUTimelineView.prototype.get scrollableElements):
(WI.CPUTimelineView.prototype.initialLayout):
(WI.CPUTimelineView.prototype.layout):
(WI.CPUTimelineView.prototype._graphPositionForMouseEvent):
(WI.CPUTimelineView.prototype._handleIndicatorClick):
(WI.CPUTimelineView.prototype._attemptSelectIndicatatorTimelineRecord):
(WI.CPUTimelineView.prototype._selectTimelineRecord):
Place the Main Thread Indicator view beneath the big graph.
Clicking inside it selects records in the Timeline Overview.

  • UserInterface/Views/CPUUsageIndicatorView.css: Added.

(.cpu-usage-indicator-view):
(.cpu-usage-indicator-view > .details):
(body[dir=ltr] .cpu-usage-indicator-view > .details):
(body[dir=rtl] .cpu-usage-indicator-view > .details):
(body[dir=rtl] .cpu-usage-indicator-view > .graph):
(.cpu-usage-indicator-view > .graph):
(.cpu-usage-indicator-view > .graph,):

  • UserInterface/Views/CPUUsageIndicatorView.js: Added.

(WI.CPUUsageIndicatorView):
(WI.CPUUsageIndicatorView.prototype.get chart):
(WI.CPUUsageIndicatorView.prototype.clear):
(WI.CPUUsageIndicatorView.prototype.updateChart):
Converts the CPU samples data into a RangeChart. It works to coalesce
many samples of the same type into a single range to reduce total ranges.

  • UserInterface/Views/TimelineRecordingContentView.js:

(WI.TimelineRecordingContentView):
(WI.TimelineRecordingContentView.prototype._recordSelected):
(WI.TimelineRecordingContentView.prototype._recordWasSelected):
(WI.TimelineRecordingContentView.prototype._selectRecordInTimelineOverview):
(WI.TimelineRecordingContentView.prototype._selectRecordInTimelineView):

  • UserInterface/Views/TimelineView.js:

Add a path for a TimelineView to dispatch a record selected event and cause
have the TimelineRecordingContentView react to it by updating the timeline
overview and relevent timeline view.

Location:
trunk/Source/WebInspectorUI
Files:
3 added
8 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/WebInspectorUI/ChangeLog

    r242080 r242104  
     12019-02-26  Joseph Pecoraro  <pecoraro@apple.com>
     2
     3        Web Inspector: CPU Usage Timeline - Main Thread Indicator
     4        https://bugs.webkit.org/show_bug.cgi?id=194972
     5
     6        Reviewed by Devin Rousso.
     7
     8        * UserInterface/Main.html:
     9        * UserInterface/Base/Utilities.js:
     10        (value):
     11        The existing enclosingNode doesn't work for SVG because its names
     12        are lowercase. Add a simplified version for the svg case.
     13
     14        * UserInterface/Views/RangeChart.js: Added.
     15        (WI.RangeChart):
     16        (WI.RangeChart.prototype.get size):
     17        (WI.RangeChart.prototype.set size):
     18        (WI.RangeChart.prototype.addRange):
     19        (WI.RangeChart.prototype.clear):
     20        (WI.RangeChart.prototype.layout):
     21        A new chart that draws rects for given ranges.
     22
     23        * UserInterface/Models/Timeline.js:
     24        (WI.Timeline.prototype.recordsOverlappingTimeRange):
     25        Helper to specifically get records touching a range. Useful
     26        for when we have a single pixel spanning (startTime -> endTime)
     27        and we want to find records in that pixel.
     28
     29        * UserInterface/Views/CPUTimelineView.css:
     30        (.timeline-view.cpu .cpu-usage-indicator-view > .graph > .range-chart rect):
     31        (.timeline-view.cpu .cpu-usage-indicator-view > .graph > .range-chart .sample-type-script):
     32        (.timeline-view.cpu .cpu-usage-indicator-view > .graph > .range-chart .sample-type-style):
     33        (.timeline-view.cpu .cpu-usage-indicator-view > .graph > .range-chart .sample-type-layout):
     34        (.timeline-view.cpu .cpu-usage-indicator-view > .graph > .range-chart .sample-type-paint):
     35        * UserInterface/Views/CPUTimelineView.js:
     36        (WI.CPUTimelineView.prototype.get indicatorViewHeight):
     37        (WI.CPUTimelineView.prototype.clear):
     38        (WI.CPUTimelineView.prototype.get scrollableElements):
     39        (WI.CPUTimelineView.prototype.initialLayout):
     40        (WI.CPUTimelineView.prototype.layout):
     41        (WI.CPUTimelineView.prototype._graphPositionForMouseEvent):
     42        (WI.CPUTimelineView.prototype._handleIndicatorClick):
     43        (WI.CPUTimelineView.prototype._attemptSelectIndicatatorTimelineRecord):
     44        (WI.CPUTimelineView.prototype._selectTimelineRecord):
     45        Place the Main Thread Indicator view beneath the big graph.
     46        Clicking inside it selects records in the Timeline Overview.
     47
     48        * UserInterface/Views/CPUUsageIndicatorView.css: Added.
     49        (.cpu-usage-indicator-view):
     50        (.cpu-usage-indicator-view > .details):
     51        (body[dir=ltr] .cpu-usage-indicator-view > .details):
     52        (body[dir=rtl] .cpu-usage-indicator-view > .details):
     53        (body[dir=rtl] .cpu-usage-indicator-view > .graph):
     54        (.cpu-usage-indicator-view > .graph):
     55        (.cpu-usage-indicator-view > .graph,):
     56        * UserInterface/Views/CPUUsageIndicatorView.js: Added.
     57        (WI.CPUUsageIndicatorView):
     58        (WI.CPUUsageIndicatorView.prototype.get chart):
     59        (WI.CPUUsageIndicatorView.prototype.clear):
     60        (WI.CPUUsageIndicatorView.prototype.updateChart):
     61        Converts the CPU samples data into a RangeChart. It works to coalesce
     62        many samples of the same type into a single range to reduce total ranges.
     63
     64        * UserInterface/Views/TimelineRecordingContentView.js:
     65        (WI.TimelineRecordingContentView):
     66        (WI.TimelineRecordingContentView.prototype._recordSelected):
     67        (WI.TimelineRecordingContentView.prototype._recordWasSelected):
     68        (WI.TimelineRecordingContentView.prototype._selectRecordInTimelineOverview):
     69        (WI.TimelineRecordingContentView.prototype._selectRecordInTimelineView):
     70        * UserInterface/Views/TimelineView.js:
     71        Add a path for a TimelineView to dispatch a record selected event and cause
     72        have the TimelineRecordingContentView react to it by updating the timeline
     73        overview and relevent timeline view.
     74
    1752019-02-26  Devin Rousso  <drousso@apple.com>
    276
  • trunk/Source/WebInspectorUI/UserInterface/Base/Utilities.js

    r242017 r242104  
    198198    value(nodeNames)
    199199    {
    200         let upperCaseNodeNames = nodeNames.map((name) => name.toUpperCase());
     200        let lowerCaseNodeNames = nodeNames.map((name) => name.toLowerCase());
    201201
    202202        for (let node = this; node; node = node.parentElement) {
    203             for (let nodeName of upperCaseNodeNames) {
    204                 if (node.nodeName === nodeName)
     203            for (let nodeName of lowerCaseNodeNames) {
     204                if (node.nodeName.toLowerCase() === nodeName)
    205205                    return node;
    206206            }
  • trunk/Source/WebInspectorUI/UserInterface/Main.html

    r242077 r242104  
    4646    <link rel="stylesheet" href="Views/CPUTimelineOverviewGraph.css">
    4747    <link rel="stylesheet" href="Views/CPUTimelineView.css">
     48    <link rel="stylesheet" href="Views/CPUUsageIndicatorView.css">
    4849    <link rel="stylesheet" href="Views/CPUUsageStackedView.css">
    4950    <link rel="stylesheet" href="Views/CPUUsageView.css">
     
    595596    <script src="Views/CPUTimelineOverviewGraph.js"></script>
    596597    <script src="Views/CPUTimelineView.js"></script>
     598    <script src="Views/CPUUsageIndicatorView.js"></script>
    597599    <script src="Views/CPUUsageStackedView.js"></script>
    598600    <script src="Views/CPUUsageView.js"></script>
     
    748750    <script src="Views/QuickConsoleNavigationBar.js"></script>
    749751    <script src="Views/RadioButtonNavigationItem.js"></script>
     752    <script src="Views/RangeChart.js"></script>
    750753    <script src="Views/RecordingActionTreeElement.js"></script>
    751754    <script src="Views/RecordingContentView.js"></script>
  • trunk/Source/WebInspectorUI/UserInterface/Models/Timeline.js

    r241315 r242104  
    9494    }
    9595
     96    recordsOverlappingTimeRange(startTime, endTime)
     97    {
     98        let lowerIndex = this._records.lowerBound(startTime, (time, record) => time - record.endTime);
     99        let upperIndex = this._records.upperBound(endTime, (time, record) => time - record.startTime);
     100
     101        return this._records.slice(lowerIndex, upperIndex);
     102    }
     103
    96104    recordsInTimeRange(startTime, endTime, includeRecordBeforeStart)
    97105    {
  • trunk/Source/WebInspectorUI/UserInterface/Views/CPUTimelineView.css

    r242077 r242104  
    215215    background-color: var(--background-color-content);
    216216}
     217
     218.timeline-view.cpu .cpu-usage-indicator-view > .graph > .range-chart rect {
     219    stroke-opacity: 0.25;
     220}
     221
     222.timeline-view.cpu .cpu-usage-indicator-view > .graph > .range-chart .sample-type-script {
     223    stroke: var(--cpu-script-stroke-color);
     224    fill: var(--cpu-script-fill-color);
     225}
     226
     227.timeline-view.cpu .cpu-usage-indicator-view > .graph > .range-chart .sample-type-style {
     228    stroke: var(--cpu-style-stroke-color);
     229    fill: var(--cpu-style-fill-color);
     230}
     231
     232.timeline-view.cpu .cpu-usage-indicator-view > .graph > .range-chart .sample-type-layout {
     233    stroke: var(--cpu-layout-stroke-color);
     234    fill: var(--cpu-layout-fill-color);
     235}
     236
     237.timeline-view.cpu .cpu-usage-indicator-view > .graph > .range-chart .sample-type-paint {
     238    stroke: var(--cpu-paint-stroke-color);
     239    fill: var(--cpu-paint-fill-color);
     240}
  • trunk/Source/WebInspectorUI/UserInterface/Views/CPUTimelineView.js

    r242073 r242104  
    5858    static get cpuUsageViewHeight() { return 150; }
    5959    static get threadCPUUsageViewHeight() { return 65; }
     60    static get indicatorViewHeight() { return 15; }
    6061
    6162    // Public
     
    105106
    106107        this._removeWorkerThreadViews();
    107     }
     108
     109        this._mainThreadWorkIndicatorView.clear();
     110    }
     111
     112    // Protected
     113
     114    get showsFilterBar() { return false; }
    108115
    109116    get scrollableElements()
     
    111118        return [this.element];
    112119    }
    113 
    114     // Protected
    115 
    116     get showsFilterBar() { return false; }
    117120
    118121    initialLayout()
     
    120123        this.element.style.setProperty("--cpu-usage-stacked-view-height", CPUTimelineView.cpuUsageViewHeight + "px");
    121124        this.element.style.setProperty("--cpu-usage-view-height", CPUTimelineView.threadCPUUsageViewHeight + "px");
     125        this.element.style.setProperty("--cpu-usage-indicator-view-height", CPUTimelineView.indicatorViewHeight + "px");
    122126
    123127        let contentElement = this.element.appendChild(document.createElement("div"));
     
    197201        this.addSubview(this._cpuUsageView);
    198202        this._detailsContainerElement.appendChild(this._cpuUsageView.element);
     203
     204        this._mainThreadWorkIndicatorView = new WI.CPUUsageIndicatorView;
     205        this.addSubview(this._mainThreadWorkIndicatorView);
     206        this._detailsContainerElement.appendChild(this._mainThreadWorkIndicatorView.element);
     207
     208        this._mainThreadWorkIndicatorView.chart.element.addEventListener("click", this._handleIndicatorClick.bind(this));
    199209
    200210        let threadsSubtitleElement = detailsContainerElement.appendChild(document.createElement("div"));
     
    516526            layoutView(workerView, "usage", CPUTimelineView.threadCPUUsageViewHeight, {dataPoints: workerData.dataPoints, min: workerData.min, max: workerData.max, average: workerData.average});
    517527        }
     528
     529        function xScaleIndicatorRange(sampleIndex) {
     530            return (sampleIndex / 1000) / secondsPerPixel;
     531        }
     532
     533        let graphWidth = (graphEndTime - graphStartTime) / secondsPerPixel;
     534        let size = new WI.Size(graphWidth, CPUTimelineView.indicatorViewHeight);
     535        this._mainThreadWorkIndicatorView.updateChart(samplingData.samples, size, visibleEndTime, xScaleIndicatorRange);
    518536    }
    519537
     
    700718            this.needsLayout();
    701719    }
     720
     721    _graphPositionForMouseEvent(event)
     722    {
     723        let svgElement = event.target.enclosingNodeOrSelfWithNodeName("svg");
     724        if (!svgElement)
     725            return NaN;
     726
     727        let svgRect = svgElement.getBoundingClientRect();
     728        let position = event.pageX - svgRect.left;
     729
     730        if (WI.resolvedLayoutDirection() === WI.LayoutDirection.RTL)
     731            return svgRect.width - position;
     732        return position;
     733    }
     734
     735    _handleIndicatorClick(event)
     736    {
     737        let clickPosition = this._graphPositionForMouseEvent(event);
     738        if (isNaN(clickPosition))
     739            return;
     740
     741        let secondsPerPixel = this._timelineRuler.secondsPerPixel;
     742        let graphClickTime = clickPosition * secondsPerPixel;
     743        let graphStartTime = this.startTime;
     744
     745        let clickStartTime = graphStartTime + graphClickTime;
     746        let clickEndTime = clickStartTime + secondsPerPixel;
     747
     748        // Try at the exact clicked pixel.
     749        if (event.target.localName === "rect") {
     750            if (this._attemptSelectIndicatatorTimelineRecord(clickStartTime, clickEndTime))
     751                return;
     752            console.assert(false, "If the user clicked on a rect there should have been a record in this pixel range");
     753        }
     754
     755        // Spiral out 4 pixels each side to try and select a nearby record.
     756        for (let i = 1, delta = 0; i <= 4; ++i) {
     757            delta += secondsPerPixel;
     758            if (this._attemptSelectIndicatatorTimelineRecord(clickStartTime - delta, clickStartTime))
     759                return;
     760            if (this._attemptSelectIndicatatorTimelineRecord(clickEndTime, clickEndTime + delta))
     761                return;
     762        }
     763    }
     764
     765    _attemptSelectIndicatatorTimelineRecord(startTime, endTime)
     766    {
     767        let layoutTimeline = this._recording.timelineForRecordType(WI.TimelineRecord.Type.Layout);
     768        let layoutRecords = layoutTimeline ? layoutTimeline.recordsOverlappingTimeRange(startTime, endTime) : [];
     769        layoutRecords = layoutRecords.filter((record) => {
     770            switch (record.eventType) {
     771            case WI.LayoutTimelineRecord.EventType.RecalculateStyles:
     772            case WI.LayoutTimelineRecord.EventType.ForcedLayout:
     773            case WI.LayoutTimelineRecord.EventType.Layout:
     774            case WI.LayoutTimelineRecord.EventType.Paint:
     775            case WI.LayoutTimelineRecord.EventType.Composite:
     776                return true;
     777            case WI.LayoutTimelineRecord.EventType.InvalidateStyles:
     778            case WI.LayoutTimelineRecord.EventType.InvalidateLayout:
     779                return false;
     780            default:
     781                console.error("Unhandled LayoutTimelineRecord.EventType", record.eventType);
     782                return false;
     783            }
     784        });
     785
     786        if (layoutRecords.length) {
     787            this._selectTimelineRecord(layoutRecords[0]);
     788            return true;
     789        }
     790
     791        let scriptTimeline = this._recording.timelineForRecordType(WI.TimelineRecord.Type.Script);
     792        let scriptRecords = scriptTimeline ? scriptTimeline.recordsOverlappingTimeRange(startTime, endTime) : [];
     793        scriptRecords = scriptRecords.filter((record) => {
     794            switch (record.eventType) {
     795            case WI.ScriptTimelineRecord.EventType.ScriptEvaluated:
     796            case WI.ScriptTimelineRecord.EventType.APIScriptEvaluated:
     797            case WI.ScriptTimelineRecord.EventType.ObserverCallback:
     798            case WI.ScriptTimelineRecord.EventType.EventDispatched:
     799            case WI.ScriptTimelineRecord.EventType.MicrotaskDispatched:
     800            case WI.ScriptTimelineRecord.EventType.TimerFired:
     801            case WI.ScriptTimelineRecord.EventType.AnimationFrameFired:
     802                return true;
     803            case WI.ScriptTimelineRecord.EventType.AnimationFrameRequested:
     804            case WI.ScriptTimelineRecord.EventType.AnimationFrameCanceled:
     805            case WI.ScriptTimelineRecord.EventType.TimerInstalled:
     806            case WI.ScriptTimelineRecord.EventType.TimerRemoved:
     807            case WI.ScriptTimelineRecord.EventType.ProbeSampleRecorded:
     808            case WI.ScriptTimelineRecord.EventType.ConsoleProfileRecorded:
     809            case WI.ScriptTimelineRecord.EventType.GarbageCollected:
     810                return false;
     811            default:
     812                console.error("Unhandled ScriptTimelineRecord.EventType", record.eventType);
     813                return false;
     814            }
     815        });
     816
     817        if (scriptRecords.length) {
     818            this._selectTimelineRecord(scriptRecords[0]);
     819            return true;
     820        }
     821
     822        return false;
     823    }
     824
     825    _selectTimelineRecord(record)
     826    {
     827        this.dispatchEventToListeners(WI.TimelineView.Event.RecordWasSelected, {record});
     828    }
    702829};
    703830
  • trunk/Source/WebInspectorUI/UserInterface/Views/TimelineRecordingContentView.js

    r241982 r242104  
    9797        WI.ContentView.addEventListener(WI.ContentView.Event.SupplementalRepresentedObjectsDidChange, this._contentViewSupplementalRepresentedObjectsDidChange, this);
    9898
    99         WI.TimelineView.addEventListener(WI.TimelineView.Event.RecordWasFiltered, this._recordWasFiltered, this);
     99        WI.TimelineView.addEventListener(WI.TimelineView.Event.RecordWasFiltered, this._handleTimelineViewRecordFiltered, this);
     100        WI.TimelineView.addEventListener(WI.TimelineView.Event.RecordWasSelected, this._handleTimelineViewRecordSelected, this);
    100101
    101102        WI.notifications.addEventListener(WI.Notification.VisibilityStateDidChange, this._inspectorVisibilityStateChanged, this);
     
    701702        let {record} = event.data;
    702703
    703         for (let timelineView of this._timelineViewMap.values()) {
    704             let recordMatchesTimeline = record && timelineView.representedObject.type === record.type;
    705 
    706             if (recordMatchesTimeline && timelineView !== this.currentTimelineView)
    707                 this.showTimelineViewForTimeline(timelineView.representedObject);
    708 
    709             if (!record || recordMatchesTimeline)
    710                 timelineView.selectRecord(record);
    711         }
     704        this._selectRecordInTimelineView(record);
    712705    }
    713706
     
    805798    }
    806799
    807     _recordWasFiltered(event)
     800    _handleTimelineViewRecordFiltered(event)
    808801    {
    809802        if (event.target !== this.currentTimelineView)
     
    821814    }
    822815
     816    _handleTimelineViewRecordSelected(event)
     817    {
     818        if (!this.visible)
     819            return;
     820
     821        let {record} = event.data;
     822
     823        this._selectRecordInTimelineOverview(record);
     824        this._selectRecordInTimelineView(record);
     825    }
     826
     827    _selectRecordInTimelineOverview(record)
     828    {
     829        let timeline = this._recording.timelineForRecordType(record.type);
     830        if (!timeline)
     831            return;
     832
     833        this._timelineOverview.selectRecord(timeline, record);
     834    }
     835
     836    _selectRecordInTimelineView(record)
     837    {
     838        for (let timelineView of this._timelineViewMap.values()) {
     839            let recordMatchesTimeline = record && timelineView.representedObject.type === record.type;
     840
     841            if (recordMatchesTimeline && timelineView !== this.currentTimelineView)
     842                this.showTimelineViewForTimeline(timelineView.representedObject);
     843
     844            if (!record || recordMatchesTimeline)
     845                timelineView.selectRecord(record);
     846        }
     847    }
     848
    823849    _updateProgressView()
    824850    {
  • trunk/Source/WebInspectorUI/UserInterface/Views/TimelineView.js

    r241953 r242104  
    336336
    337337WI.TimelineView.Event = {
    338     RecordWasFiltered: "record-was-filtered"
     338    RecordWasFiltered: "timeline-view-record-was-filtered",
     339    RecordWasSelected: "timeline-view-record-was-selected",
    339340};
Note: See TracChangeset for help on using the changeset viewer.