Web Inspector: discontinuous recordings should have discontinuities in the timeline...
authormattbaker@apple.com <mattbaker@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 4 Jun 2016 21:09:55 +0000 (21:09 +0000)
committermattbaker@apple.com <mattbaker@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sat, 4 Jun 2016 21:09:55 +0000 (21:09 +0000)
https://bugs.webkit.org/show_bug.cgi?id=158052
<rdar://problem/26516695>

Reviewed by Joseph Pecoraro.

Add gaps to the overview and category line charts where discontinuities
exist in the timeline recording.

* UserInterface/Models/Timeline.js:
(WebInspector.Timeline.prototype.recordsInTimeRange):
Replaces `_visibleRecords` methods found in various views.

* UserInterface/Models/TimelineRecording.js:
(WebInspector.TimelineRecording.prototype.reset):
(WebInspector.TimelineRecording.prototype.addDiscontinuity):
(WebInspector.TimelineRecording.prototype.discontinuitiesInTimeRange):
Allow discontinuities to be added to the recording, and have a means to
look up gaps within a time range.

* UserInterface/Views/HeapAllocationsTimelineOverviewGraph.js:
(WebInspector.HeapAllocationsTimelineOverviewGraph.prototype._visibleRecords): Deleted.
Replaced by Timeline helper method.

* UserInterface/Views/MemoryTimelineOverviewGraph.js:
(WebInspector.MemoryTimelineOverviewGraph.prototype.layout.insertDiscontinuity):
(WebInspector.MemoryTimelineOverviewGraph.prototype.layout):
Insert zero-points into the chart at discontinuity boundaries to create
gaps. Data points for records immediately before or after a gap are extended
to the edge of the discontinuity.

(WebInspector.MemoryTimelineOverviewGraph.prototype._visibleRecords): Deleted.
Replaced by Timeline helper method.

* UserInterface/Views/MemoryTimelineView.js:
(WebInspector.MemoryTimelineView.prototype.layout):
Insert zero-points into each category chart at discontinuity boundaries
to create gaps.

Insert zero-points into the chart to create gaps.
(WebInspector.MemoryTimelineView.prototype._visibleRecords): Deleted.
Replaced by Timeline helper method.

* UserInterface/Views/TimelineOverview.js:
(WebInspector.TimelineOverview.prototype.discontinuitiesInTimeRange):
Forward to the TimelineRecording, which isn't exposed to clients.

* UserInterface/Views/TimelineRecordingContentView.js:
(WebInspector.TimelineRecordingContentView):
(WebInspector.TimelineRecordingContentView.prototype._capturingStarted):
(WebInspector.TimelineRecordingContentView.prototype._capturingStopped):
(WebInspector.TimelineRecordingContentView.prototype._recordingReset):
Track discontinuities (recording stop followed by a start) and add them
to the current recording.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@201686 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebInspectorUI/ChangeLog
Source/WebInspectorUI/UserInterface/Models/Timeline.js
Source/WebInspectorUI/UserInterface/Models/TimelineRecording.js
Source/WebInspectorUI/UserInterface/Views/HeapAllocationsTimelineOverviewGraph.js
Source/WebInspectorUI/UserInterface/Views/MemoryTimelineOverviewGraph.js
Source/WebInspectorUI/UserInterface/Views/MemoryTimelineView.js
Source/WebInspectorUI/UserInterface/Views/TimelineOverview.js
Source/WebInspectorUI/UserInterface/Views/TimelineRecordingContentView.js

index 79a7822..686681a 100644 (file)
@@ -1,3 +1,60 @@
+2016-06-04  Matt Baker  <mattbaker@apple.com>
+
+        Web Inspector: discontinuous recordings should have discontinuities in the timeline memory graph
+        https://bugs.webkit.org/show_bug.cgi?id=158052
+        <rdar://problem/26516695>
+
+        Reviewed by Joseph Pecoraro.
+
+        Add gaps to the overview and category line charts where discontinuities
+        exist in the timeline recording.
+
+        * UserInterface/Models/Timeline.js:
+        (WebInspector.Timeline.prototype.recordsInTimeRange):
+        Replaces `_visibleRecords` methods found in various views.
+
+        * UserInterface/Models/TimelineRecording.js:
+        (WebInspector.TimelineRecording.prototype.reset):
+        (WebInspector.TimelineRecording.prototype.addDiscontinuity):
+        (WebInspector.TimelineRecording.prototype.discontinuitiesInTimeRange):
+        Allow discontinuities to be added to the recording, and have a means to
+        look up gaps within a time range.
+
+        * UserInterface/Views/HeapAllocationsTimelineOverviewGraph.js:
+        (WebInspector.HeapAllocationsTimelineOverviewGraph.prototype._visibleRecords): Deleted.
+        Replaced by Timeline helper method.
+
+        * UserInterface/Views/MemoryTimelineOverviewGraph.js:
+        (WebInspector.MemoryTimelineOverviewGraph.prototype.layout.insertDiscontinuity):
+        (WebInspector.MemoryTimelineOverviewGraph.prototype.layout):
+        Insert zero-points into the chart at discontinuity boundaries to create
+        gaps. Data points for records immediately before or after a gap are extended
+        to the edge of the discontinuity.
+
+        (WebInspector.MemoryTimelineOverviewGraph.prototype._visibleRecords): Deleted.
+        Replaced by Timeline helper method.
+
+        * UserInterface/Views/MemoryTimelineView.js:
+        (WebInspector.MemoryTimelineView.prototype.layout):
+        Insert zero-points into each category chart at discontinuity boundaries
+        to create gaps.
+
+        Insert zero-points into the chart to create gaps.
+        (WebInspector.MemoryTimelineView.prototype._visibleRecords): Deleted.
+        Replaced by Timeline helper method.
+
+        * UserInterface/Views/TimelineOverview.js:
+        (WebInspector.TimelineOverview.prototype.discontinuitiesInTimeRange):
+        Forward to the TimelineRecording, which isn't exposed to clients.
+
+        * UserInterface/Views/TimelineRecordingContentView.js:
+        (WebInspector.TimelineRecordingContentView):
+        (WebInspector.TimelineRecordingContentView.prototype._capturingStarted):
+        (WebInspector.TimelineRecordingContentView.prototype._capturingStopped):
+        (WebInspector.TimelineRecordingContentView.prototype._recordingReset):
+        Track discontinuities (recording stop followed by a start) and add them
+        to the current recording.
+
 2016-06-03  Brian Burg  <bburg@apple.com>
 
         Web Inspector: add a keyboard shortcut to close the current tab bar item
index c7d5921..2c32bb2 100644 (file)
@@ -103,6 +103,18 @@ WebInspector.Timeline = class Timeline extends WebInspector.Object
         this.dispatchEventToListeners(WebInspector.Timeline.Event.Refreshed);
     }
 
+    recordsInTimeRange(startTime, endTime, includeRecordBeforeStart)
+    {
+        let lowerIndex = this._records.lowerBound(startTime, (time, record) => time - record.timestamp);
+        let upperIndex = this._records.upperBound(endTime, (time, record) => time - record.timestamp);
+
+        // Include the record right before the start time.
+        if (includeRecordBeforeStart && lowerIndex > 0)
+            lowerIndex--;
+
+        return this._records.slice(lowerIndex, upperIndex);
+    }
+
     // Private
 
     _updateTimesIfNeeded(record)
index 932568d..95e419f 100644 (file)
@@ -159,6 +159,7 @@ WebInspector.TimelineRecording = class TimelineRecording extends WebInspector.Ob
         this._eventMarkers = [];
         this._startTime = NaN;
         this._endTime = NaN;
+        this._discontinuities = [];
 
         this._topDownCallingContextTree.reset();
         this._bottomUpCallingContextTree.reset();
@@ -282,6 +283,16 @@ WebInspector.TimelineRecording = class TimelineRecording extends WebInspector.Ob
         memoryTimeline.addMemoryPressureEvent(memoryPressureEvent);
     }
 
+    addDiscontinuity(startTime, endTime)
+    {
+        this._discontinuities.push({startTime, endTime});
+    }
+
+    discontinuitiesInTimeRange(startTime, endTime)
+    {
+        return this._discontinuities.filter((item) => item.startTime < endTime && item.endTime > startTime);
+    }
+
     computeElapsedTime(timestamp)
     {
         if (!timestamp || isNaN(timestamp))
index 51db814..bb5c2e4 100644 (file)
@@ -57,7 +57,7 @@ WebInspector.HeapAllocationsTimelineOverviewGraph = class HeapAllocationsTimelin
         this._selectedImageElement = null;
 
         // This may display records past the current time marker.
-        let visibleRecords = this._visibleRecords(this.startTime, this.endTime);
+        let visibleRecords = this._heapAllocations.recordsInTimeRange(this.startTime, this.endTime);
         if (!visibleRecords.length)
             return;
 
@@ -114,14 +114,6 @@ WebInspector.HeapAllocationsTimelineOverviewGraph = class HeapAllocationsTimelin
         this._selectedImageElement = imageElement;
     }
 
-    _visibleRecords(startTime, endTime)
-    {
-        let records = this._heapAllocationsTimeline.records;
-        let lowerIndex = records.lowerBound(startTime, (time, record) => time - record.timestamp);
-        let upperIndex = records.upperBound(endTime, (time, record) => time - record.timestamp);
-        return records.slice(lowerIndex, upperIndex);
-    }
-
     _heapAllocationTimelineRecordAdded(event)
     {
         this.needsLayout();
index 2c44282..ad7ea4d 100644 (file)
@@ -133,7 +133,13 @@ WebInspector.MemoryTimelineOverviewGraph = class MemoryTimelineOverviewGraph ext
                 element.remove();
         }
 
-        let visibleRecords = this._visibleRecords(graphStartTime, visibleEndTime);
+        let discontinuities = this.timelineOverview.discontinuitiesInTimeRange(graphStartTime, visibleEndTime);
+
+        // Don't include the record before the graph start if the graph start is within a gap.
+        let includeRecordBeforeStart = !discontinuities.length || discontinuities[0].startTime > graphStartTime;
+
+        // FIXME: <https://webkit.org/b/153759> Web Inspector: Memory Timelines should better extend to future data
+        let visibleRecords = this._memoryTimeline.recordsInTimeRange(graphStartTime, visibleEndTime, includeRecordBeforeStart);
         if (!visibleRecords.length)
             return;
 
@@ -148,19 +154,59 @@ WebInspector.MemoryTimelineOverviewGraph = class MemoryTimelineOverviewGraph ext
         }
 
         // Extend the first record to the start so it doesn't look like we originate at zero size.
-        if (visibleRecords[0] === this._memoryTimeline.records[0])
+        if (visibleRecords[0] === this._memoryTimeline.records[0] && (!discontinuities.length || discontinuities[0].startTime > visibleRecords[0].startTime))
             this._chart.addPointSet(0, pointSetForRecord(visibleRecords[0]));
 
+        function insertDiscontinuity(previousRecord, discontinuity, nextRecord)
+        {
+            console.assert(previousRecord || nextRecord);
+            if (!(previousRecord || nextRecord))
+                return;
+
+            let xStart = xScale(discontinuity.startTime);
+            let xEnd = xScale(discontinuity.endTime);
+
+            // Extend the previous record to the start of the discontinuity.
+            if (previousRecord)
+                this._chart.addPointSet(xStart, pointSetForRecord(previousRecord));
+
+            let zeroValues = Array((previousRecord || nextRecord).categories.length).fill(yScale(0));
+            this._chart.addPointSet(xStart, zeroValues);
+
+            if (nextRecord) {
+                this._chart.addPointSet(xEnd, zeroValues);
+                this._chart.addPointSet(xEnd, pointSetForRecord(nextRecord));
+            } else {
+                // Extend the discontinuity to the visible end time to prevent
+                // drawing artifacts when the next record arrives.
+                this._chart.addPointSet(xScale(visibleEndTime), zeroValues);
+            }
+        }
+
         // Points for visible records.
+        let previousRecord = null;
         for (let record of visibleRecords) {
+            if (discontinuities.length && discontinuities[0].endTime < record.startTime) {
+                let discontinuity = discontinuities.shift();
+                insertDiscontinuity.call(this, previousRecord, discontinuity, record);
+            }
+
             let x = xScale(record.startTime);
             this._chart.addPointSet(x, pointSetForRecord(record));
+
+            previousRecord = record;
         }
 
-        // Extend the last value to current / end time.
-        let lastRecord = visibleRecords.lastValue;
-        let x = Math.floor(xScale(visibleEndTime));
-        this._chart.addPointSet(x, pointSetForRecord(lastRecord));
+        if (discontinuities.length)
+            insertDiscontinuity.call(this, previousRecord, discontinuities[0], null);
+        else {
+            // Extend the last value to current / end time.
+            let lastRecord = visibleRecords.lastValue;
+            if (lastRecord.startTime <= visibleEndTime) {
+                let x = Math.floor(xScale(visibleEndTime));
+                this._chart.addPointSet(x, pointSetForRecord(lastRecord));
+            }
+        }
 
         this._chart.updateLayout();
     }
@@ -194,22 +240,6 @@ WebInspector.MemoryTimelineOverviewGraph = class MemoryTimelineOverviewGraph ext
         return events.slice(lowerIndex, upperIndex);
     }
 
-    _visibleRecords(startTime, endTime)
-    {
-        let records = this._memoryTimeline.records;
-        let lowerIndex = records.lowerBound(startTime, (time, record) => time - record.timestamp);
-        let upperIndex = records.upperBound(endTime, (time, record) => time - record.timestamp);
-
-        // Include the record right before the start time in case it extends into this range.
-        if (lowerIndex > 0)
-            lowerIndex--;
-        // FIXME: <https://webkit.org/b/153759> Web Inspector: Memory Timelines should better extend to future data
-        // if (upperIndex !== records.length)
-        //     upperIndex++;
-
-        return records.slice(lowerIndex, upperIndex);
-    }
-
     _memoryTimelineRecordAdded(event)
     {
         let memoryTimelineRecord = event.data.record;
index 549adc1..3c95739 100644 (file)
@@ -29,6 +29,8 @@ WebInspector.MemoryTimelineView = class MemoryTimelineView extends WebInspector.
     {
         super(timeline, extraArguments);
 
+        this._recording = extraArguments.recording;
+
         console.assert(timeline.type === WebInspector.TimelineRecord.Type.Memory, timeline);
 
         this.element.classList.add("memory");
@@ -175,7 +177,13 @@ WebInspector.MemoryTimelineView = class MemoryTimelineView extends WebInspector.
         let graphCurrentTime = this.currentTime;
         let visibleEndTime = Math.min(this.endTime, this.currentTime);
 
-        let visibleRecords = this._visibleRecords(graphStartTime, visibleEndTime);
+        let discontinuities = this._recording.discontinuitiesInTimeRange(graphStartTime, visibleEndTime);
+
+        // Don't include the record before the graph start if the graph start is within a gap.
+        let includeRecordBeforeStart = !discontinuities.length || discontinuities[0].startTime > graphStartTime;
+
+        // FIXME: <https://webkit.org/b/153759> Web Inspector: Memory Timelines should better extend to future data
+        let visibleRecords = this.representedObject.recordsInTimeRange(graphStartTime, visibleEndTime, includeRecordBeforeStart);
         if (!visibleRecords.length)
             return;
 
@@ -205,14 +213,35 @@ WebInspector.MemoryTimelineView = class MemoryTimelineView extends WebInspector.
 
         for (let record of visibleRecords) {
             let time = record.startTime;
+            let discontinuity = null;
+            if (discontinuities.length && discontinuities[0].endTime < time)
+                discontinuity = discontinuities.shift();
+
             for (let category of record.categories) {
                 let categoryData = categoryDataMap[category.type];
+
+                if (discontinuity) {
+                    if (categoryData.dataPoints.length) {
+                        let previousDataPoint = categoryData.dataPoints.lastValue;
+                        categoryData.dataPoints.push({time: discontinuity.startTime, size: previousDataPoint.size});
+                    }
+
+                    categoryData.dataPoints.push({time: discontinuity.startTime, size: 0});
+                    categoryData.dataPoints.push({time: discontinuity.endTime, size: 0});
+                    categoryData.dataPoints.push({time: discontinuity.endTime, size: category.size});
+                }
+
                 categoryData.dataPoints.push({time, size: category.size});
                 categoryData.max = Math.max(categoryData.max, category.size);
                 categoryData.min = Math.min(categoryData.min, category.size);
             }
         }
 
+        // If the graph end time is inside a gap, the last data point should
+        // only be extended to the start of the discontinuity.
+        if (discontinuities.length)
+            visibleEndTime = discontinuities[0].startTime;
+
         function layoutCategoryView(categoryView, categoryData) {
             let {dataPoints, min, max} = categoryData;
 
@@ -314,22 +343,6 @@ WebInspector.MemoryTimelineView = class MemoryTimelineView extends WebInspector.
         totalElement.textContent = (percent === 100 ? percent : (percent - 0.05).toFixed(1)) + "%";
     }
 
-    _visibleRecords(startTime, endTime)
-    {
-        let records = this.representedObject.records;
-        let lowerIndex = records.lowerBound(startTime, (time, record) => time - record.timestamp);
-        let upperIndex = records.upperBound(endTime, (time, record) => time - record.timestamp);
-
-        // Include the record right before the start time in case it extends into this range.
-        if (lowerIndex > 0)
-            lowerIndex--;
-        // FIXME: <https://webkit.org/b/153759> Web Inspector: Memory Timelines should better extend to future data
-        // if (upperIndex !== records.length)
-        //     upperIndex++;
-
-        return records.slice(lowerIndex, upperIndex);
-    }
-
     _initializeCategoryViews(record)
     {
         console.assert(!this._didInitializeCategories, "Should only initialize category views once");
index 1e1efbc..6267e57 100644 (file)
@@ -422,6 +422,11 @@ WebInspector.TimelineOverview = class TimelineOverview extends WebInspector.View
         }
     }
 
+    discontinuitiesInTimeRange(startTime, endTime)
+    {
+        return this._recording.discontinuitiesInTimeRange(startTime, endTime);
+    }
+
     // Protected
 
     get timelineRuler()
index 0202537..020bbef 100644 (file)
@@ -71,6 +71,7 @@ WebInspector.TimelineRecordingContentView = class TimelineRecordingContentView e
 
         this._updating = false;
         this._currentTime = NaN;
+        this._discontinuityStartTime = NaN;
         this._lastUpdateTimestamp = NaN;
         this._startTimeNeedsReset = true;
         this._renderingFrameTimeline = null;
@@ -470,9 +471,18 @@ WebInspector.TimelineRecordingContentView = class TimelineRecordingContentView e
     {
         this._updateProgressView();
 
+        let startTime = event.data.startTime;
         if (!this._updating)
-            this._startUpdatingCurrentTime(event.data.startTime);
+            this._startUpdatingCurrentTime(startTime);
         this._clearTimelineNavigationItem.enabled = !this._recording.readonly;
+
+        // A discontinuity occurs when the recording is stopped and resumed at
+        // a future time. Capturing started signals the end of the current
+        // discontinuity, if one exists.
+        if (!isNaN(this._discontinuityStartTime)) {
+            this._recording.addDiscontinuity(this._discontinuityStartTime, startTime);
+            this._discontinuityStartTime = NaN;
+        }
     }
 
     _capturingStopped(event)
@@ -484,6 +494,8 @@ WebInspector.TimelineRecordingContentView = class TimelineRecordingContentView e
 
         if (this.currentTimelineView)
             this._updateTimelineViewTimes(this.currentTimelineView);
+
+        this._discontinuityStartTime = event.data.endTime || this._currentTime;
     }
 
     _debuggerPaused(event)
@@ -603,6 +615,7 @@ WebInspector.TimelineRecordingContentView = class TimelineRecordingContentView e
     _recordingReset(event)
     {
         this._currentTime = NaN;
+        this._discontinuityStartTime = NaN;
 
         if (!this._updating) {
             // Force the time ruler and views to reset to 0.