Web Inspector: CPU Usage Timeline
authorcommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 25 Jan 2019 01:06:17 +0000 (01:06 +0000)
committercommit-queue@webkit.org <commit-queue@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 25 Jan 2019 01:06:17 +0000 (01:06 +0000)
https://bugs.webkit.org/show_bug.cgi?id=193730
<rdar://problem/46797201>

Patch by Joseph Pecoraro <pecoraro@apple.com> on 2019-01-24
Reviewed by Devin Rousso.

Source/JavaScriptCore:

* CMakeLists.txt:
* DerivedSources-input.xcfilelist:
* DerivedSources.make:
New files.

* inspector/protocol/CPUProfiler.json: Added.
New domain that follows the pattern of Memory/ScriptProfiler.

* inspector/protocol/Timeline.json:
New enum to auto-start a CPU instrument in the backend.

Source/WebCore:

Test: inspector/cpu-profiler/tracking.html

* Sources.txt:
* UnifiedSources-input.xcfilelist:
* WebCore.xcodeproj/project.pbxproj:
New files.

* inspector/InspectorController.cpp:
(WebCore::InspectorController::createLazyAgents):
* inspector/InstrumentingAgents.cpp:
(WebCore::InstrumentingAgents::reset):
* inspector/InstrumentingAgents.h:
(WebCore::InstrumentingAgents::inspectorCPUProfilerAgent const):
(WebCore::InstrumentingAgents::setInspectorCPUProfilerAgent):
Create and track the CPUProfilerAgent.

* inspector/agents/InspectorTimelineAgent.cpp:
(WebCore::InspectorTimelineAgent::toggleInstruments):
(WebCore::InspectorTimelineAgent::toggleCPUInstrument):
Handle backend auto-start of the CPU instrument / timeline.

* inspector/agents/InspectorCPUProfilerAgent.h:
* inspector/agents/InspectorCPUProfilerAgent.cpp: Added.
(WebCore::InspectorCPUProfilerAgent::InspectorCPUProfilerAgent):
(WebCore::InspectorCPUProfilerAgent::didCreateFrontendAndBackend):
(WebCore::InspectorCPUProfilerAgent::willDestroyFrontendAndBackend):
(WebCore::InspectorCPUProfilerAgent::startTracking):
(WebCore::InspectorCPUProfilerAgent::stopTracking):
(WebCore::InspectorCPUProfilerAgent::collectSample):
CPUProfilerAgent uses the ResourceUsageThread to get CPU data.

* inspector/agents/InspectorTimelineAgent.h:
* inspector/agents/InspectorMemoryAgent.cpp:
(WebCore::InspectorMemoryAgent::startTracking):
(WebCore::InspectorMemoryAgent::collectSample):
Update the MemoryAgent to collect only Memory data and use a more accurate sample timestamp.

* page/ResourceUsageData.h:
* page/ResourceUsageThread.cpp:
(WebCore::ResourceUsageThread::addObserver):
(WebCore::ResourceUsageThread::removeObserver):
(WebCore::ResourceUsageThread::notifyObservers):
(WebCore::ResourceUsageThread::recomputeCollectionMode):
(WebCore::ResourceUsageThread::threadBody):
* page/ResourceUsageThread.h:
* page/cocoa/ResourceUsageOverlayCocoa.mm:
(WebCore::ResourceUsageOverlay::platformInitialize):
* page/cocoa/ResourceUsageThreadCocoa.mm:
(WebCore::ResourceUsageThread::platformCollectCPUData):
(WebCore::ResourceUsageThread::platformCollectMemoryData):
(WebCore::ResourceUsageThread::platformThreadBody): Deleted.
* page/linux/ResourceUsageOverlayLinux.cpp:
(WebCore::ResourceUsageOverlay::platformInitialize):
* page/linux/ResourceUsageThreadLinux.cpp:
(WebCore::ResourceUsageThread::platformCollectCPUData):
(WebCore::ResourceUsageThread::platformCollectMemoryData):
(WebCore::ResourceUsageThread::platformThreadBody):
Give each observer their own collection mode. The ResourceUsageThread
will then collect data that is the union of all of the active observers.
This allows collecting CPU and Memory data separately, reducing the cost
of each when gathered individually.

Source/WebInspectorUI:

CPU Usage is gathered in the backend twice a second, the frequency of the
ResourceUsageThread in WebCore. The frontend displays cpu usage in a few
ways in the Timeline.

We use a column chart in the timeline overview to display the frequency and
relative distance of samples. This helps show if the samples were close
together or far apart, which indicates how meaningful they will be at a
particular scale.

We use a line chart in the timeline detail view which will be easier to see
the changes over a particular time range selection.

* Localizations/en.lproj/localizedStrings.js:
New strings.

* UserInterface/Main.html:
* UserInterface/Base/Main.js:
(WI.loaded):
* UserInterface/Test.html:
* UserInterface/Test/Test.js:
(WI.loaded):
* UserInterface/Protocol/CPUProfilerObserver.js:
(WI.CPUProfilerObserver.prototype.trackingStart):
(WI.CPUProfilerObserver.prototype.trackingUpdate):
(WI.CPUProfilerObserver.prototype.trackingComplete):
(WI.CPUProfilerObserver):
New files and default registration.

* UserInterface/Protocol/Target.js:
(WI.Target.prototype.get CPUProfilerAgent):
New Agent.

* UserInterface/Controllers/TimelineManager.js:
(WI.TimelineManager.availableTimelineTypes):
(WI.TimelineManager.prototype.cpuProfilerTrackingStarted):
(WI.TimelineManager.prototype.cpuProfilerTrackingUpdated):
(WI.TimelineManager.prototype.cpuProfilerTrackingCompleted):
(WI.TimelineManager.prototype._updateAutoCaptureInstruments):
(WI.TimelineManager.prototype.memoryTrackingStart): Renamed.
(WI.TimelineManager.prototype.memoryTrackingUpdate): Renamed.
(WI.TimelineManager.prototype.memoryTrackingComplete): Renamed.
* UserInterface/Models/CPUInstrument.js:
(WI.CPUInstrument):
(WI.CPUInstrument.supported):
(WI.CPUInstrument.prototype.get timelineRecordType):
(WI.CPUInstrument.prototype.startInstrumentation):
(WI.CPUInstrument.prototype.stopInstrumentation):
* UserInterface/Models/CPUTimelineRecord.js:
(WI.CPUTimelineRecord):
(WI.CPUTimelineRecord.prototype.get timestamp):
(WI.CPUTimelineRecord.prototype.get usage):
* UserInterface/Models/Instrument.js:
(WI.Instrument.createForTimelineType):
* UserInterface/Models/TimelineRecord.js:
* UserInterface/Models/TimelineRecording.js:
(WI.TimelineRecording.prototype.addRecord):
Expose a new CPU instrument and timeline.

* UserInterface/Views/ColumnChart.js: Added.
(WI.ColumnChart):
(WI.ColumnChart.prototype.get element):
(WI.ColumnChart.prototype.get bars):
(WI.ColumnChart.prototype.get size):
(WI.ColumnChart.prototype.set size):
(WI.ColumnChart.prototype.addBar):
(WI.ColumnChart.prototype.clear):
(WI.ColumnChart.prototype.needsLayout):
(WI.ColumnChart.prototype.updateLayout):
View that will draw vertical bars with independent widths.
This is meant to be used similiar to WI.LineChart.

* UserInterface/Images/CPUInstrument.svg: Added.
* UserInterface/Views/Variables.css:
(:root):
CPU timeline colors and icon.

* UserInterface/Views/CPUTimelineOverviewGraph.css:
(body .sidebar > .panel.navigation.timeline > .timelines-content li.item.cpu,):
(.timeline-overview-graph.cpu):
(.timeline-overview-graph.cpu > .legend):
(body[dir=ltr] .timeline-overview-graph.cpu > .legend):
(body[dir=rtl] .timeline-overview-graph.cpu > .legend):
(.timeline-overview-graph:nth-child(even) > .legend):
(body[dir=rtl] .timeline-overview-graph.cpu > .bar-chart):
(.timeline-overview-graph.cpu > .bar-chart > svg > g > rect):
* UserInterface/Views/CPUTimelineOverviewGraph.js: Added.
(WI.CPUTimelineOverviewGraph):
(WI.CPUTimelineOverviewGraph.prototype.get height):
(WI.CPUTimelineOverviewGraph.prototype.reset):
(WI.CPUTimelineOverviewGraph.prototype.layout.xScale):
(WI.CPUTimelineOverviewGraph.prototype.layout.yScale):
(WI.CPUTimelineOverviewGraph.prototype.layout.yScaleForRecord):
(WI.CPUTimelineOverviewGraph.prototype.layout):
(WI.CPUTimelineOverviewGraph.prototype._updateLegend):
(WI.CPUTimelineOverviewGraph.prototype._cpuTimelineRecordAdded):
* UserInterface/Views/CPUTimelineView.css:
(.timeline-view.cpu):
(.timeline-view.cpu > .content):
(.timeline-view.cpu > .content .subtitle):
(.timeline-view.cpu > .content > .details):
(.timeline-view.cpu > .content > .details > .timeline-ruler):
(body[dir=ltr] .timeline-view.cpu > .content > .details > .timeline-ruler):
(body[dir=rtl] .timeline-view.cpu > .content > .details > .timeline-ruler):
(.timeline-view.cpu > .content > .details > .subtitle):
(.cpu-usage-view .line-chart > svg > path):
(.timeline-view.cpu .legend > .row > .swatch.current):
* UserInterface/Views/CPUTimelineView.js: Added.
(WI.CPUTimelineView):
(WI.CPUTimelineView.prototype.shown):
(WI.CPUTimelineView.prototype.hidden):
(WI.CPUTimelineView.prototype.closed):
(WI.CPUTimelineView.prototype.reset):
(WI.CPUTimelineView.prototype.get scrollableElements):
(WI.CPUTimelineView.prototype.get showsFilterBar):
(WI.CPUTimelineView.prototype.layout.layoutView):
(WI.CPUTimelineView.prototype.layout.xScale):
(WI.CPUTimelineView.prototype.layout.yScale):
(WI.CPUTimelineView.prototype.layout):
(WI.CPUTimelineView.prototype._cpuTimelineRecordAdded):
* UserInterface/Views/CPUUsageView.css:
(.cpu-usage-view):
(.cpu-usage-view > .details):
(body[dir=ltr] .cpu-usage-view > .details):
(body[dir=rtl] .cpu-usage-view > .details):
(.cpu-usage-view > .graph):
(body[dir=rtl] .cpu-usage-view > .graph):
* UserInterface/Views/CPUUsageView.js:
(WI.CPUUsageView):
(WI.CPUUsageView.prototype.get element):
(WI.CPUUsageView.prototype.clear):
(WI.CPUUsageView.prototype.layoutWithDataPoints):
(WI.CPUUsageView.prototype._updateDetails):
* UserInterface/Views/ContentView.js:
(WI.ContentView.createFromRepresentedObject):
* UserInterface/Views/TimelineIcons.css:
(.cpu-icon .icon):
* UserInterface/Views/TimelineOverviewGraph.js:
(WI.TimelineOverviewGraph.createForTimeline):
* UserInterface/Views/TimelineTabContentView.js:
(WI.TimelineTabContentView.displayNameForTimelineType):
(WI.TimelineTabContentView.iconClassNameForTimelineType):
(WI.TimelineTabContentView.genericClassNameForTimelineType):
(WI.TimelineTabContentView.iconClassNameForRecord):
(WI.TimelineTabContentView.displayNameForRecord):
Timeline views for CPU usage.

* UserInterface/Views/MemoryCategoryView.js:
(WI.MemoryCategoryView):
* UserInterface/Views/MemoryTimelineView.js:
(WI.MemoryTimelineView.createChartContainer):
(WI.MemoryTimelineView):
(WI.MemoryTimelineView.prototype._clearMaxComparisonLegend):
Minor updates to style and comments.

LayoutTests:

* inspector/cpu-profiler/tracking-expected.txt: Added.
* inspector/cpu-profiler/tracking.html: Added.
Test the CPUProfiler domain emits events.

* inspector/heap/tracking-expected.txt:
* inspector/heap/tracking.html:
* inspector/memory/tracking-expected.txt:
* inspector/memory/tracking.html:
* inspector/script-profiler/tracking-expected.txt:
* inspector/script-profiler/tracking.html:
Update test naming.

* platform/win/TestExpectations:
Skip on platforms without RESOURCE_USAGE.

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

70 files changed:
LayoutTests/ChangeLog
LayoutTests/inspector/cpu-profiler/tracking-expected.txt [new file with mode: 0644]
LayoutTests/inspector/cpu-profiler/tracking.html [new file with mode: 0644]
LayoutTests/inspector/heap/tracking-expected.txt
LayoutTests/inspector/heap/tracking.html
LayoutTests/inspector/memory/tracking-expected.txt
LayoutTests/inspector/memory/tracking.html
LayoutTests/inspector/script-profiler/tracking-expected.txt
LayoutTests/inspector/script-profiler/tracking.html
LayoutTests/platform/win/TestExpectations
Source/JavaScriptCore/CMakeLists.txt
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/DerivedSources-input.xcfilelist
Source/JavaScriptCore/DerivedSources.make
Source/JavaScriptCore/inspector/protocol/CPUProfiler.json [new file with mode: 0644]
Source/JavaScriptCore/inspector/protocol/Timeline.json
Source/WebCore/ChangeLog
Source/WebCore/Sources.txt
Source/WebCore/UnifiedSources-input.xcfilelist
Source/WebCore/WebCore.xcodeproj/project.pbxproj
Source/WebCore/inspector/InspectorController.cpp
Source/WebCore/inspector/InstrumentingAgents.cpp
Source/WebCore/inspector/InstrumentingAgents.h
Source/WebCore/inspector/agents/InspectorCPUProfilerAgent.cpp [new file with mode: 0644]
Source/WebCore/inspector/agents/InspectorCPUProfilerAgent.h [new file with mode: 0644]
Source/WebCore/inspector/agents/InspectorMemoryAgent.cpp
Source/WebCore/inspector/agents/InspectorTimelineAgent.cpp
Source/WebCore/inspector/agents/InspectorTimelineAgent.h
Source/WebCore/page/ResourceUsageData.h
Source/WebCore/page/ResourceUsageThread.cpp
Source/WebCore/page/ResourceUsageThread.h
Source/WebCore/page/cocoa/ResourceUsageOverlayCocoa.mm
Source/WebCore/page/cocoa/ResourceUsageThreadCocoa.mm
Source/WebCore/page/linux/ResourceUsageOverlayLinux.cpp
Source/WebCore/page/linux/ResourceUsageThreadLinux.cpp
Source/WebInspectorUI/ChangeLog
Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js
Source/WebInspectorUI/UserInterface/Base/Main.js
Source/WebInspectorUI/UserInterface/Controllers/TimelineManager.js
Source/WebInspectorUI/UserInterface/Debug/UncaughtExceptionReporter.css
Source/WebInspectorUI/UserInterface/Images/CPUInstrument.svg [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Main.html
Source/WebInspectorUI/UserInterface/Models/CPUInstrument.js [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Models/CPUTimelineRecord.js [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Models/Instrument.js
Source/WebInspectorUI/UserInterface/Models/TimelineRecord.js
Source/WebInspectorUI/UserInterface/Models/TimelineRecording.js
Source/WebInspectorUI/UserInterface/Protocol/CPUProfilerObserver.js [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Protocol/MemoryObserver.js
Source/WebInspectorUI/UserInterface/Protocol/Target.js
Source/WebInspectorUI/UserInterface/Test.html
Source/WebInspectorUI/UserInterface/Test/Test.js
Source/WebInspectorUI/UserInterface/Views/CPUTimelineOverviewGraph.css [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Views/CPUTimelineOverviewGraph.js [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Views/CPUTimelineView.css [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Views/CPUTimelineView.js [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Views/CPUUsageView.css [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Views/CPUUsageView.js [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Views/ColumnChart.js [new file with mode: 0644]
Source/WebInspectorUI/UserInterface/Views/ContentView.js
Source/WebInspectorUI/UserInterface/Views/MemoryCategoryView.css
Source/WebInspectorUI/UserInterface/Views/MemoryCategoryView.js
Source/WebInspectorUI/UserInterface/Views/MemoryTimelineOverviewGraph.js
Source/WebInspectorUI/UserInterface/Views/MemoryTimelineView.css
Source/WebInspectorUI/UserInterface/Views/MemoryTimelineView.js
Source/WebInspectorUI/UserInterface/Views/ResourceSizesContentView.css
Source/WebInspectorUI/UserInterface/Views/TimelineIcons.css
Source/WebInspectorUI/UserInterface/Views/TimelineOverviewGraph.js
Source/WebInspectorUI/UserInterface/Views/TimelineTabContentView.js
Source/WebInspectorUI/UserInterface/Views/Variables.css

index a8c8f6e..2f09999 100644 (file)
@@ -1,3 +1,26 @@
+2019-01-24  Joseph Pecoraro  <pecoraro@apple.com>
+
+        Web Inspector: CPU Usage Timeline
+        https://bugs.webkit.org/show_bug.cgi?id=193730
+        <rdar://problem/46797201>
+
+        Reviewed by Devin Rousso.
+
+        * inspector/cpu-profiler/tracking-expected.txt: Added.
+        * inspector/cpu-profiler/tracking.html: Added.
+        Test the CPUProfiler domain emits events.
+
+        * inspector/heap/tracking-expected.txt:
+        * inspector/heap/tracking.html:
+        * inspector/memory/tracking-expected.txt:
+        * inspector/memory/tracking.html:
+        * inspector/script-profiler/tracking-expected.txt:
+        * inspector/script-profiler/tracking.html:
+        Update test naming.
+
+        * platform/win/TestExpectations:
+        Skip on platforms without RESOURCE_USAGE.
+
 2019-01-24  Wenson Hsieh  <wenson_hsieh@apple.com>
 
         [iOS] Unable to make a selection in jsfiddle.net using arrow keys when requesting desktop site
diff --git a/LayoutTests/inspector/cpu-profiler/tracking-expected.txt b/LayoutTests/inspector/cpu-profiler/tracking-expected.txt
new file mode 100644 (file)
index 0000000..5b9cfc0
--- /dev/null
@@ -0,0 +1,14 @@
+Tests that CPUProfiler.startTracking and CPUProfiler.stopTracking trigger trackingStart, trackingUpdate, and trackingComplete events with expected data.
+
+
+== Running test suite: CPUProfiler.Tracking
+-- Running test case: CPUProfiler.Tracking.StartAndStopTrackingWithEvent
+CPUProfiler.trackingStart
+PASS: Should have a timestamp when starting.
+CPUProfiler.trackingUpdate
+PASS: Should have an event object.
+PASS: Event should have a timestamp.
+PASS: Event should have a usage.
+PASS: usage should be greater than or equal to zero.
+CPUProfiler.trackingComplete
+
diff --git a/LayoutTests/inspector/cpu-profiler/tracking.html b/LayoutTests/inspector/cpu-profiler/tracking.html
new file mode 100644 (file)
index 0000000..4500faf
--- /dev/null
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../http/tests/inspector/resources/protocol-test.js"></script>
+<script>
+function test()
+{
+    let suite = ProtocolTest.createAsyncSuite("CPUProfiler.Tracking");
+
+    suite.addTestCase({
+        name: "CPUProfiler.Tracking.StartAndStopTrackingWithEvent",
+        test(resolve, reject) {
+            InspectorProtocol.awaitEvent({event: "CPUProfiler.trackingStart"}).then((messageObject) => {
+                ProtocolTest.log("CPUProfiler.trackingStart");
+                ProtocolTest.expectThat(typeof messageObject.params.timestamp === "number", "Should have a timestamp when starting.");
+            });
+
+            InspectorProtocol.awaitEvent({event: "CPUProfiler.trackingUpdate"}).then((messageObject) => {
+                ProtocolTest.log("CPUProfiler.trackingUpdate");
+                ProtocolTest.expectThat(typeof messageObject.params.event === "object", "Should have an event object.");
+                ProtocolTest.expectThat(typeof messageObject.params.event.timestamp === "number", "Event should have a timestamp.");
+                ProtocolTest.expectThat(typeof messageObject.params.event.usage === "number", "Event should have a usage.");
+                ProtocolTest.expectThat(messageObject.params.event.usage >= 0, "usage should be greater than or equal to zero.");
+
+                InspectorProtocol.sendCommand("CPUProfiler.stopTracking", {});
+            });
+
+            InspectorProtocol.awaitEvent({event: "CPUProfiler.trackingComplete"}).then((messageObject) => {
+                ProtocolTest.log("CPUProfiler.trackingComplete");
+                resolve();
+            });
+
+            InspectorProtocol.sendCommand("CPUProfiler.startTracking", {});
+        }
+    });
+
+    suite.runTestCasesAndFinish();
+}
+</script>
+</head>
+<body onload="runTest()">
+<p>Tests that CPUProfiler.startTracking and CPUProfiler.stopTracking trigger trackingStart, trackingUpdate, and trackingComplete events with expected data.</p>
+</body>
+</html>
index 27d049f..847900e 100644 (file)
@@ -1,8 +1,8 @@
 Tests that Heap.startTracking and Heap.stopTracking trigger trackingStart and trackingComplete events with expected data.
 
 
-== Running test suite: Heap.startTracking and Heap.stopTracking
--- Running test case: StartAndStopTrackingIncludeSnapshots
+== Running test suite: Heap.Tracking
+-- Running test case: Heap.Tracking.StartAndStopTrackingIncludeSnapshots
 Heap.trackingStart
 PASS: Should have a timestamp when starting.
 PASS: Should have snapshotData when starting.
index 2bb83d0..a5699c6 100644 (file)
@@ -5,10 +5,10 @@
 <script>
 function test()
 {
-    let suite = ProtocolTest.createAsyncSuite("Heap.startTracking and Heap.stopTracking");
+    let suite = ProtocolTest.createAsyncSuite("Heap.Tracking");
 
     suite.addTestCase({
-        name: "StartAndStopTrackingIncludeSnapshots",
+        name: "Heap.Tracking.StartAndStopTrackingIncludeSnapshots",
         test(resolve, reject) {
             InspectorProtocol.awaitEvent({event: "Heap.trackingStart"}).then((messageObject) => {
                 ProtocolTest.log("Heap.trackingStart");
index 7687ae5..8cc3aaf 100644 (file)
@@ -1,8 +1,8 @@
 Tests that Memory.startTracking and Memory.stopTracking trigger trackingStart, trackingUpdate, and trackingComplete events with expected data.
 
 
-== Running test suite: Memory.startTracking and Memory.stopTracking
--- Running test case: StartAndStopTrackingWithEvent
+== Running test suite: Memory.Tracking
+-- Running test case: Memory.Tracking.StartAndStopTrackingWithEvent
 Memory.trackingStart
 PASS: Should have a timestamp when starting.
 Memory.trackingUpdate
index bee202a..11d7e7a 100644 (file)
@@ -5,10 +5,10 @@
 <script>
 function test()
 {
-    let suite = ProtocolTest.createAsyncSuite("Memory.startTracking and Memory.stopTracking");
+    let suite = ProtocolTest.createAsyncSuite("Memory.Tracking");
 
     suite.addTestCase({
-        name: "StartAndStopTrackingWithEvent",
+        name: "Memory.Tracking.StartAndStopTrackingWithEvent",
         test(resolve, reject) {
             InspectorProtocol.awaitEvent({event: "Memory.trackingStart"}).then((messageObject) => {
                 ProtocolTest.log("Memory.trackingStart");
index 3634ea8..ae0a6e4 100644 (file)
@@ -1,8 +1,8 @@
 Tests that ScriptProfiler.startTracking and ScriptProfiler.stopTracking trigger trackingStart and trackingComplete events.
 
 
-== Running test suite: ScriptProfiler.startTracking and ScriptProfiler.stopTracking
--- Running test case: StartAndStopTracking
+== Running test suite: ScriptProfiler.Tracking
+-- Running test case: ScriptProfiler.Tracking.StartAndStopTracking
 ScriptProfiler.trackingStart
 PASS: Should have a timestamp when starting.
 ScriptProfiler.trackingComplete
index dfa097f..c23af04 100644 (file)
@@ -5,10 +5,10 @@
 <script>
 function test()
 {
-    let suite = ProtocolTest.createAsyncSuite("ScriptProfiler.startTracking and ScriptProfiler.stopTracking");
+    let suite = ProtocolTest.createAsyncSuite("ScriptProfiler.Tracking");
 
     suite.addTestCase({
-        name: "StartAndStopTracking",
+        name: "ScriptProfiler.Tracking.StartAndStopTracking",
         description: "Start and stop the ScriptProfiler should cause trackingStart and trackingComplete events.",
         test(resolve, reject) {
             InspectorProtocol.awaitEvent({event: "ScriptProfiler.trackingStart"}).then((messageObject) => {
index 94f3bc1..cf73a46 100644 (file)
@@ -2398,6 +2398,7 @@ webkit.org/b/137157 http/tests/inspector [ Skip ]
 
 # Requires ENABLE(RESOURCE_USAGE).
 inspector/memory [ Skip ]
+inspector/cpu-profiler [ Skip ]
 
 # webkit.org/b/143548 inspector/console [ Skip ]
 # webkit.org/b/137157 inspector/css [ Skip ]
index 878efe7..de05300 100644 (file)
@@ -1096,6 +1096,7 @@ endif ()
 
 if (ENABLE_RESOURCE_USAGE)
     list(APPEND JavaScriptCore_INSPECTOR_DOMAINS
+        ${JAVASCRIPTCORE_DIR}/inspector/protocol/CPUProfiler.json
         ${JAVASCRIPTCORE_DIR}/inspector/protocol/Memory.json
     )
 endif ()
index 5faea62..de140b7 100644 (file)
@@ -1,3 +1,22 @@
+2019-01-24  Joseph Pecoraro  <pecoraro@apple.com>
+
+        Web Inspector: CPU Usage Timeline
+        https://bugs.webkit.org/show_bug.cgi?id=193730
+        <rdar://problem/46797201>
+
+        Reviewed by Devin Rousso.
+
+        * CMakeLists.txt:
+        * DerivedSources-input.xcfilelist:
+        * DerivedSources.make:
+        New files.
+
+        * inspector/protocol/CPUProfiler.json: Added.
+        New domain that follows the pattern of Memory/ScriptProfiler.
+
+        * inspector/protocol/Timeline.json:
+        New enum to auto-start a CPU instrument in the backend.
+
 2019-01-24  Yusuke Suzuki  <ysuzuki@apple.com>
 
         [JSC] SharedArrayBufferConstructor and ArrayBufferConstructor should not have their own IsoSubspace
index 20e86fd..b5f47a6 100644 (file)
@@ -77,6 +77,7 @@ $(PROJECT_DIR)/generator/main.rb
 $(PROJECT_DIR)/inspector/InjectedScriptSource.js
 $(PROJECT_DIR)/inspector/protocol/ApplicationCache.json
 $(PROJECT_DIR)/inspector/protocol/Audit.json
+$(PROJECT_DIR)/inspector/protocol/CPUProfiler.json
 $(PROJECT_DIR)/inspector/protocol/CSS.json
 $(PROJECT_DIR)/inspector/protocol/Canvas.json
 $(PROJECT_DIR)/inspector/protocol/Console.json
index 011a220..d9e2c8b 100644 (file)
@@ -258,6 +258,7 @@ ifeq ($(findstring ENABLE_INDEXED_DATABASE,$(FEATURE_DEFINES)), ENABLE_INDEXED_D
 endif
 
 ifeq ($(findstring ENABLE_RESOURCE_USAGE,$(FEATURE_DEFINES)), ENABLE_RESOURCE_USAGE)
+    INSPECTOR_DOMAINS := $(INSPECTOR_DOMAINS) $(JavaScriptCore)/inspector/protocol/CPUProfiler.json
     INSPECTOR_DOMAINS := $(INSPECTOR_DOMAINS) $(JavaScriptCore)/inspector/protocol/Memory.json
 endif
 
diff --git a/Source/JavaScriptCore/inspector/protocol/CPUProfiler.json b/Source/JavaScriptCore/inspector/protocol/CPUProfiler.json
new file mode 100644 (file)
index 0000000..2db2b50
--- /dev/null
@@ -0,0 +1,46 @@
+{
+    "domain": "CPUProfiler",
+    "description": "CPUProfiler domain exposes cpu usage tracking.",
+    "featureGuard": "ENABLE(RESOURCE_USAGE)",
+    "availability": ["web"],
+    "types": [
+        {
+            "id": "Event",
+            "type": "object",
+            "properties": [
+                { "name": "timestamp", "type": "number" },
+                { "name": "usage", "type": "number", "description": "Percent of total cpu usage. If there are multiple cores the usage may be greater than 100%." }
+            ]
+        }
+    ],
+    "commands": [
+        {
+            "name": "startTracking",
+            "description": "Start tracking cpu usage."
+        },
+        {
+            "name": "stopTracking",
+            "description": "Stop tracking cpu usage. This will produce a `trackingComplete` event."
+        }
+    ],
+    "events": [
+        {
+            "name": "trackingStart",
+            "description": "Tracking started.",
+            "parameters": [
+                { "name": "timestamp", "type": "number" }
+            ]
+        },
+        {
+            "name": "trackingUpdate",
+            "description": "Periodic tracking updates with event data.",
+            "parameters": [
+                { "name": "event", "$ref": "Event" }
+            ]
+        },
+        {
+            "name": "trackingComplete",
+            "description": "Tracking stopped. Includes any buffered data during tracking, such as profiling information."
+        }
+    ]
+}
index 6b55c29..b9c4f84 100644 (file)
@@ -39,6 +39,7 @@
             "enum": [
                 "ScriptProfiler",
                 "Timeline",
+                "CPU",
                 "Memory",
                 "Heap"
             ]
index a112c3f..3e89623 100644 (file)
@@ -1,3 +1,73 @@
+2019-01-24  Joseph Pecoraro  <pecoraro@apple.com>
+
+        Web Inspector: CPU Usage Timeline
+        https://bugs.webkit.org/show_bug.cgi?id=193730
+        <rdar://problem/46797201>
+
+        Reviewed by Devin Rousso.
+
+        Test: inspector/cpu-profiler/tracking.html
+
+        * Sources.txt:
+        * UnifiedSources-input.xcfilelist:
+        * WebCore.xcodeproj/project.pbxproj:
+        New files.
+
+        * inspector/InspectorController.cpp:
+        (WebCore::InspectorController::createLazyAgents):
+        * inspector/InstrumentingAgents.cpp:
+        (WebCore::InstrumentingAgents::reset):
+        * inspector/InstrumentingAgents.h:
+        (WebCore::InstrumentingAgents::inspectorCPUProfilerAgent const):
+        (WebCore::InstrumentingAgents::setInspectorCPUProfilerAgent):
+        Create and track the CPUProfilerAgent.
+
+        * inspector/agents/InspectorTimelineAgent.cpp:
+        (WebCore::InspectorTimelineAgent::toggleInstruments):
+        (WebCore::InspectorTimelineAgent::toggleCPUInstrument):
+        Handle backend auto-start of the CPU instrument / timeline.
+
+        * inspector/agents/InspectorCPUProfilerAgent.h:
+        * inspector/agents/InspectorCPUProfilerAgent.cpp: Added.
+        (WebCore::InspectorCPUProfilerAgent::InspectorCPUProfilerAgent):
+        (WebCore::InspectorCPUProfilerAgent::didCreateFrontendAndBackend):
+        (WebCore::InspectorCPUProfilerAgent::willDestroyFrontendAndBackend):
+        (WebCore::InspectorCPUProfilerAgent::startTracking):
+        (WebCore::InspectorCPUProfilerAgent::stopTracking):
+        (WebCore::InspectorCPUProfilerAgent::collectSample):
+        CPUProfilerAgent uses the ResourceUsageThread to get CPU data.
+
+        * inspector/agents/InspectorTimelineAgent.h:
+        * inspector/agents/InspectorMemoryAgent.cpp:
+        (WebCore::InspectorMemoryAgent::startTracking):
+        (WebCore::InspectorMemoryAgent::collectSample):
+        Update the MemoryAgent to collect only Memory data and use a more accurate sample timestamp.
+
+        * page/ResourceUsageData.h:
+        * page/ResourceUsageThread.cpp:
+        (WebCore::ResourceUsageThread::addObserver):
+        (WebCore::ResourceUsageThread::removeObserver):
+        (WebCore::ResourceUsageThread::notifyObservers):
+        (WebCore::ResourceUsageThread::recomputeCollectionMode):
+        (WebCore::ResourceUsageThread::threadBody):
+        * page/ResourceUsageThread.h:
+        * page/cocoa/ResourceUsageOverlayCocoa.mm:
+        (WebCore::ResourceUsageOverlay::platformInitialize):
+        * page/cocoa/ResourceUsageThreadCocoa.mm:
+        (WebCore::ResourceUsageThread::platformCollectCPUData):
+        (WebCore::ResourceUsageThread::platformCollectMemoryData):
+        (WebCore::ResourceUsageThread::platformThreadBody): Deleted.
+        * page/linux/ResourceUsageOverlayLinux.cpp:
+        (WebCore::ResourceUsageOverlay::platformInitialize):
+        * page/linux/ResourceUsageThreadLinux.cpp:
+        (WebCore::ResourceUsageThread::platformCollectCPUData):
+        (WebCore::ResourceUsageThread::platformCollectMemoryData):
+        (WebCore::ResourceUsageThread::platformThreadBody):
+        Give each observer their own collection mode. The ResourceUsageThread
+        will then collect data that is the union of all of the active observers.
+        This allows collecting CPU and Memory data separately, reducing the cost
+        of each when gathered individually.
+
 2019-01-24  Charles Vazac  <cvazac@akamai.com>
 
         Implement PerformanceObserver.supportedEntryTypes
index 40fa474..e49d19c 100644 (file)
@@ -1286,6 +1286,7 @@ inspector/WorkerInspectorController.cpp
 inspector/WorkerScriptDebugServer.cpp
 
 inspector/agents/InspectorApplicationCacheAgent.cpp
+inspector/agents/InspectorCPUProfilerAgent.cpp
 inspector/agents/InspectorCSSAgent.cpp
 inspector/agents/InspectorCanvasAgent.cpp
 inspector/agents/InspectorDOMAgent.cpp
index 476411c..9f248b7 100644 (file)
@@ -2201,6 +2201,7 @@ $(SRCROOT)/inspector/WebInjectedScriptManager.cpp
 $(SRCROOT)/inspector/WorkerInspectorController.cpp
 $(SRCROOT)/inspector/WorkerScriptDebugServer.cpp
 $(SRCROOT)/inspector/agents/InspectorApplicationCacheAgent.cpp
+$(SRCROOT)/inspector/agents/InspectorCPUProfilerAgent.cpp
 $(SRCROOT)/inspector/agents/InspectorCSSAgent.cpp
 $(SRCROOT)/inspector/agents/InspectorCanvasAgent.cpp
 $(SRCROOT)/inspector/agents/InspectorDOMAgent.cpp
index c3d8fdb..db3ff5c 100644 (file)
                A58C59D11E382EAE0047859C /* JSPerformanceMark.h in Headers */ = {isa = PBXBuildFile; fileRef = A58C59CD1E382EA90047859C /* JSPerformanceMark.h */; };
                A58C59D31E382EB20047859C /* JSPerformanceMeasure.h in Headers */ = {isa = PBXBuildFile; fileRef = A58C59CF1E382EA90047859C /* JSPerformanceMeasure.h */; };
                A593CF8B1840535200BFCE27 /* InspectorWebAgentBase.h in Headers */ = {isa = PBXBuildFile; fileRef = A593CF8A1840535200BFCE27 /* InspectorWebAgentBase.h */; settings = {ATTRIBUTES = (Private, ); }; };
+               A59C230A21F29206004EC939 /* InspectorCPUProfilerAgent.h in Headers */ = {isa = PBXBuildFile; fileRef = A59C230821F29206004EC939 /* InspectorCPUProfilerAgent.h */; };
                A5A2AF0C1829734300DE1729 /* PageDebuggable.h in Headers */ = {isa = PBXBuildFile; fileRef = A5A2AF0A1829734300DE1729 /* PageDebuggable.h */; };
                A5A7AA43132F0ECC00D3A3C2 /* AutocapitalizeTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = A5A7AA42132F0ECC00D3A3C2 /* AutocapitalizeTypes.h */; settings = {ATTRIBUTES = (Private, ); }; };
                A5A9933D1E37FB19005B5E4D /* PerformanceObserver.h in Headers */ = {isa = PBXBuildFile; fileRef = A5A993361E37FAFD005B5E4D /* PerformanceObserver.h */; };
                A58C59CE1E382EA90047859C /* JSPerformanceMeasure.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSPerformanceMeasure.cpp; sourceTree = "<group>"; };
                A58C59CF1E382EA90047859C /* JSPerformanceMeasure.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSPerformanceMeasure.h; sourceTree = "<group>"; };
                A593CF8A1840535200BFCE27 /* InspectorWebAgentBase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InspectorWebAgentBase.h; sourceTree = "<group>"; };
+               A59C230621F29205004EC939 /* InspectorCPUProfilerAgent.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = InspectorCPUProfilerAgent.cpp; sourceTree = "<group>"; };
+               A59C230821F29206004EC939 /* InspectorCPUProfilerAgent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = InspectorCPUProfilerAgent.h; sourceTree = "<group>"; };
                A5A2AF091829734300DE1729 /* PageDebuggable.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PageDebuggable.cpp; sourceTree = "<group>"; };
                A5A2AF0A1829734300DE1729 /* PageDebuggable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PageDebuggable.h; sourceTree = "<group>"; };
                A5A7AA42132F0ECC00D3A3C2 /* AutocapitalizeTypes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AutocapitalizeTypes.h; sourceTree = "<group>"; };
                                A5B81C971FAA44260037D1E6 /* InspectorApplicationCacheAgent.h */,
                                A5B81C961FAA44260037D1E6 /* InspectorCanvasAgent.cpp */,
                                A5B81C911FAA44260037D1E6 /* InspectorCanvasAgent.h */,
+                               A59C230621F29205004EC939 /* InspectorCPUProfilerAgent.cpp */,
+                               A59C230821F29206004EC939 /* InspectorCPUProfilerAgent.h */,
                                A5B81C921FAA44260037D1E6 /* InspectorCSSAgent.cpp */,
                                A5B81CA11FAA44270037D1E6 /* InspectorCSSAgent.h */,
                                A5B81CA51FAA44270037D1E6 /* InspectorDatabaseAgent.cpp */,
                                F55B3DC21251F12D003EF269 /* ImageInputType.h in Headers */,
                                089582560E857A7E00F82C83 /* ImageLoader.h in Headers */,
                                BC7F44A80B9E324E00A9D081 /* ImageObserver.h in Headers */,
+                               A59C230A21F29206004EC939 /* InspectorCPUProfilerAgent.h in Headers */,
                                2D5A5931152525D00036EE51 /* ImageOrientation.h in Headers */,
                                B51A2F3F17D7D3AE0072517A /* ImageQualityController.h in Headers */,
                                49291E4B134172C800E753DE /* ImageRenderingMode.h in Headers */,
index 866210c..be94652 100644 (file)
@@ -39,6 +39,7 @@
 #include "Frame.h"
 #include "GraphicsContext.h"
 #include "InspectorApplicationCacheAgent.h"
+#include "InspectorCPUProfilerAgent.h"
 #include "InspectorCSSAgent.h"
 #include "InspectorCanvasAgent.h"
 #include "InspectorClient.h"
@@ -205,6 +206,7 @@ void InspectorController::createLazyAgents()
     m_agents.append(std::make_unique<InspectorIndexedDBAgent>(pageContext, m_pageAgent));
 #endif
 #if ENABLE(RESOURCE_USAGE)
+    m_agents.append(std::make_unique<InspectorCPUProfilerAgent>(pageContext));
     m_agents.append(std::make_unique<InspectorMemoryAgent>(pageContext));
 #endif
     m_agents.append(std::make_unique<PageAuditAgent>(pageContext));
index c5970a2..b5886c4 100644 (file)
@@ -57,6 +57,7 @@ void InstrumentingAgents::reset()
     m_persistentInspectorTimelineAgent = nullptr;
     m_inspectorDOMStorageAgent = nullptr;
 #if ENABLE(RESOURCE_USAGE)
+    m_inspectorCPUProfilerAgent = nullptr;
     m_inspectorMemoryAgent = nullptr;
 #endif
     m_inspectorDatabaseAgent = nullptr;
index c58dd5e..5477732 100644 (file)
@@ -44,18 +44,19 @@ class InspectorDebuggerAgent;
 namespace WebCore {
 
 class InspectorApplicationCacheAgent;
-class InspectorCanvasAgent;
+class InspectorCPUProfilerAgent;
 class InspectorCSSAgent;
+class InspectorCanvasAgent;
 class InspectorDOMAgent;
 class InspectorDOMDebuggerAgent;
 class InspectorDOMStorageAgent;
 class InspectorDatabaseAgent;
 class InspectorLayerTreeAgent;
-class InspectorWorkerAgent;
 class InspectorMemoryAgent;
 class InspectorNetworkAgent;
 class InspectorPageAgent;
 class InspectorTimelineAgent;
+class InspectorWorkerAgent;
 class Page;
 class PageDebuggerAgent;
 class PageHeapAgent;
@@ -111,6 +112,9 @@ public:
     void setInspectorDOMStorageAgent(InspectorDOMStorageAgent* agent) { m_inspectorDOMStorageAgent = agent; }
 
 #if ENABLE(RESOURCE_USAGE)
+    InspectorCPUProfilerAgent* inspectorCPUProfilerAgent() const { return m_inspectorCPUProfilerAgent; }
+    void setInspectorCPUProfilerAgent(InspectorCPUProfilerAgent* agent) { m_inspectorCPUProfilerAgent = agent; }
+
     InspectorMemoryAgent* inspectorMemoryAgent() const { return m_inspectorMemoryAgent; }
     void setInspectorMemoryAgent(InspectorMemoryAgent* agent) { m_inspectorMemoryAgent = agent; }
 #endif
@@ -157,6 +161,7 @@ private:
     InspectorTimelineAgent* m_persistentInspectorTimelineAgent { nullptr };
     InspectorDOMStorageAgent* m_inspectorDOMStorageAgent { nullptr };
 #if ENABLE(RESOURCE_USAGE)
+    InspectorCPUProfilerAgent* m_inspectorCPUProfilerAgent { nullptr };
     InspectorMemoryAgent* m_inspectorMemoryAgent { nullptr };
 #endif
     InspectorDatabaseAgent* m_inspectorDatabaseAgent { nullptr };
diff --git a/Source/WebCore/inspector/agents/InspectorCPUProfilerAgent.cpp b/Source/WebCore/inspector/agents/InspectorCPUProfilerAgent.cpp
new file mode 100644 (file)
index 0000000..66248b9
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "InspectorCPUProfilerAgent.h"
+
+#if ENABLE(RESOURCE_USAGE)
+
+#include "InstrumentingAgents.h"
+#include "ResourceUsageThread.h"
+#include <JavaScriptCore/InspectorEnvironment.h>
+#include <wtf/Stopwatch.h>
+
+namespace WebCore {
+
+using namespace Inspector;
+
+InspectorCPUProfilerAgent::InspectorCPUProfilerAgent(PageAgentContext& context)
+    : InspectorAgentBase("CPUProfiler"_s, context)
+    , m_frontendDispatcher(std::make_unique<Inspector::CPUProfilerFrontendDispatcher>(context.frontendRouter))
+    , m_backendDispatcher(Inspector::CPUProfilerBackendDispatcher::create(context.backendDispatcher, this))
+{
+}
+
+void InspectorCPUProfilerAgent::didCreateFrontendAndBackend(FrontendRouter*, BackendDispatcher*)
+{
+    m_instrumentingAgents.setInspectorCPUProfilerAgent(this);
+}
+
+void InspectorCPUProfilerAgent::willDestroyFrontendAndBackend(DisconnectReason)
+{
+    m_instrumentingAgents.setInspectorCPUProfilerAgent(nullptr);
+}
+
+void InspectorCPUProfilerAgent::startTracking(ErrorString&)
+{
+    if (m_tracking)
+        return;
+
+    ResourceUsageThread::addObserver(this, CPU, [this] (const ResourceUsageData& data) {
+        collectSample(data);
+    });
+
+    m_tracking = true;
+
+    m_frontendDispatcher->trackingStart(m_environment.executionStopwatch()->elapsedTime().seconds());
+}
+
+void InspectorCPUProfilerAgent::stopTracking(ErrorString&)
+{
+    if (!m_tracking)
+        return;
+
+    ResourceUsageThread::removeObserver(this);
+
+    m_tracking = false;
+
+    m_frontendDispatcher->trackingComplete();
+}
+
+void InspectorCPUProfilerAgent::collectSample(const ResourceUsageData& data)
+{
+    auto event = Protocol::CPUProfiler::Event::create()
+        .setTimestamp(m_environment.executionStopwatch()->elapsedTimeSince(data.timestamp).seconds())
+        .setUsage(data.cpu)
+        .release();
+
+    m_frontendDispatcher->trackingUpdate(WTFMove(event));
+}
+
+} // namespace WebCore
+
+#endif // ENABLE(RESOURCE_USAGE)
diff --git a/Source/WebCore/inspector/agents/InspectorCPUProfilerAgent.h b/Source/WebCore/inspector/agents/InspectorCPUProfilerAgent.h
new file mode 100644 (file)
index 0000000..a542abf
--- /dev/null
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#if ENABLE(RESOURCE_USAGE)
+
+#include "InspectorWebAgentBase.h"
+#include "ResourceUsageData.h"
+#include <JavaScriptCore/InspectorBackendDispatchers.h>
+#include <JavaScriptCore/InspectorFrontendDispatchers.h>
+
+namespace WebCore {
+
+typedef String ErrorString;
+
+class InspectorCPUProfilerAgent final : public InspectorAgentBase, public Inspector::CPUProfilerBackendDispatcherHandler {
+    WTF_MAKE_NONCOPYABLE(InspectorCPUProfilerAgent);
+    WTF_MAKE_FAST_ALLOCATED;
+public:
+    InspectorCPUProfilerAgent(PageAgentContext&);
+    virtual ~InspectorCPUProfilerAgent() = default;
+
+    void didCreateFrontendAndBackend(Inspector::FrontendRouter*, Inspector::BackendDispatcher*) override;
+    void willDestroyFrontendAndBackend(Inspector::DisconnectReason) override;
+
+    // CPUProfilerBackendDispatcherHandler
+    void startTracking(ErrorString&) override;
+    void stopTracking(ErrorString&) override;
+
+private:
+    void collectSample(const ResourceUsageData&);
+
+    std::unique_ptr<Inspector::CPUProfilerFrontendDispatcher> m_frontendDispatcher;
+    RefPtr<Inspector::CPUProfilerBackendDispatcher> m_backendDispatcher;
+    bool m_tracking { false };
+};
+
+} // namespace WebCore
+
+#endif // ENABLE(RESOURCE_USAGE)
index 768a6da..5bc56ea 100644 (file)
@@ -74,7 +74,7 @@ void InspectorMemoryAgent::startTracking(ErrorString&)
     if (m_tracking)
         return;
 
-    ResourceUsageThread::addObserver(this, [this] (const ResourceUsageData& data) {
+    ResourceUsageThread::addObserver(this, Memory, [this] (const ResourceUsageData& data) {
         collectSample(data);
     });
 
@@ -145,13 +145,13 @@ void InspectorMemoryAgent::collectSample(const ResourceUsageData& data)
     categories->addItem(WTFMove(otherCategory));
 
     auto event = Protocol::Memory::Event::create()
-        .setTimestamp(m_environment.executionStopwatch()->elapsedTime().seconds())
+        .setTimestamp(m_environment.executionStopwatch()->elapsedTimeSince(data.timestamp).seconds())
         .setCategories(WTFMove(categories))
         .release();
 
     m_frontendDispatcher->trackingUpdate(WTFMove(event));
 }
 
-} // namespace Inspector
+} // namespace WebCore
 
 #endif // ENABLE(RESOURCE_USAGE)
index 2e270d4..0505bd3 100644 (file)
@@ -36,6 +36,7 @@
 #include "DOMWindow.h"
 #include "Event.h"
 #include "Frame.h"
+#include "InspectorCPUProfilerAgent.h"
 #include "InspectorMemoryAgent.h"
 #include "InspectorPageAgent.h"
 #include "InstrumentingAgents.h"
@@ -514,6 +515,10 @@ void InspectorTimelineAgent::toggleInstruments(InstrumentState state)
             toggleHeapInstrument(state);
             break;
         }
+        case Inspector::Protocol::Timeline::Instrument::CPU: {
+            toggleCPUInstrument(state);
+            break;
+        }
         case Inspector::Protocol::Timeline::Instrument::Memory: {
             toggleMemoryInstrument(state);
             break;
@@ -549,6 +554,21 @@ void InspectorTimelineAgent::toggleHeapInstrument(InstrumentState state)
     }
 }
 
+void InspectorTimelineAgent::toggleCPUInstrument(InstrumentState state)
+{
+#if ENABLE(RESOURCE_USAGE)
+    if (InspectorCPUProfilerAgent* cpuProfilerAgent = m_instrumentingAgents.inspectorCPUProfilerAgent()) {
+        ErrorString unused;
+        if (state == InstrumentState::Start)
+            cpuProfilerAgent->startTracking(unused);
+        else
+            cpuProfilerAgent->stopTracking(unused);
+    }
+#else
+    UNUSED_PARAM(state);
+#endif
+}
+
 void InspectorTimelineAgent::toggleMemoryInstrument(InstrumentState state)
 {
 #if ENABLE(RESOURCE_USAGE)
index 4033cdf..82847c6 100644 (file)
@@ -164,6 +164,7 @@ private:
     void toggleInstruments(InstrumentState);
     void toggleScriptProfilerInstrument(InstrumentState);
     void toggleHeapInstrument(InstrumentState);
+    void toggleCPUInstrument(InstrumentState);
     void toggleMemoryInstrument(InstrumentState);
     void toggleTimelineInstrument(InstrumentState);
     void disableBreakpoints();
index c620a21..ab977b5 100644 (file)
@@ -82,6 +82,7 @@ struct ResourceUsageData {
 WEBCORE_EACH_MEMORY_CATEGORIES(WEBCORE_DEFINE_MEMORY_CATEGORY)
 #undef WEBCORE_DEFINE_MEMORY_CATEGORY
     } };
+    MonotonicTime timestamp { MonotonicTime::now() };
     MonotonicTime timeOfNextEdenCollection { MonotonicTime::nan() };
     MonotonicTime timeOfNextFullCollection { MonotonicTime::nan() };
 };
index 28b7e66..d9dd3e7 100644 (file)
@@ -46,7 +46,7 @@ ResourceUsageThread& ResourceUsageThread::singleton()
     return resourceUsageThread;
 }
 
-void ResourceUsageThread::addObserver(void* key, std::function<void (const ResourceUsageData&)> function)
+void ResourceUsageThread::addObserver(void* key, ResourceUsageCollectionMode mode, std::function<void (const ResourceUsageData&)> function)
 {
     auto& resourceUsageThread = ResourceUsageThread::singleton();
     resourceUsageThread.createThreadIfNeeded();
@@ -54,7 +54,9 @@ void ResourceUsageThread::addObserver(void* key, std::function<void (const Resou
     {
         LockHolder locker(resourceUsageThread.m_lock);
         bool wasEmpty = resourceUsageThread.m_observers.isEmpty();
-        resourceUsageThread.m_observers.set(key, function);
+        resourceUsageThread.m_observers.set(key, std::make_pair(mode, function));
+
+        resourceUsageThread.recomputeCollectionMode();
 
         if (wasEmpty)
             resourceUsageThread.m_condition.notifyAll();
@@ -68,6 +70,8 @@ void ResourceUsageThread::removeObserver(void* key)
     {
         LockHolder locker(resourceUsageThread.m_lock);
         resourceUsageThread.m_observers.remove(key);
+
+        resourceUsageThread.recomputeCollectionMode();
     }
 }
 
@@ -81,19 +85,27 @@ void ResourceUsageThread::waitUntilObservers()
 void ResourceUsageThread::notifyObservers(ResourceUsageData&& data)
 {
     callOnMainThread([data = WTFMove(data)]() mutable {
-        Vector<std::function<void (const ResourceUsageData&)>> functions;
-        
+        Vector<std::pair<ResourceUsageCollectionMode, std::function<void (const ResourceUsageData&)>>> pairs;
+
         {
             auto& resourceUsageThread = ResourceUsageThread::singleton();
             LockHolder locker(resourceUsageThread.m_lock);
-            functions = copyToVector(resourceUsageThread.m_observers.values());
+            pairs = copyToVector(resourceUsageThread.m_observers.values());
         }
 
-        for (auto& function : functions)
-            function(data);
+        for (auto& pair : pairs)
+            pair.second(data);
     });
 }
 
+void ResourceUsageThread::recomputeCollectionMode()
+{
+    m_collectionMode = None;
+
+    for (auto& pair : m_observers.values())
+        m_collectionMode = static_cast<ResourceUsageCollectionMode>(m_collectionMode | pair.first);
+}
+
 void ResourceUsageThread::createThreadIfNeeded()
 {
     if (m_thread)
@@ -114,9 +126,16 @@ NO_RETURN void ResourceUsageThread::threadBody()
         auto start = WallTime::now();
 
         ResourceUsageData data;
-        platformThreadBody(m_vm, data);
+        ResourceUsageCollectionMode mode = m_collectionMode;
+        if (mode & CPU)
+            platformCollectCPUData(m_vm, data);
+        if (mode & Memory)
+            platformCollectMemoryData(m_vm, data);
+
         notifyObservers(WTFMove(data));
 
+        // NOTE: Web Inspector expects this interval to be 500ms (CPU / Memory timelines),
+        // so if this interval changes Web Inspector may need to change.
         auto duration = WallTime::now() - start;
         auto difference = 500_ms - duration;
         WTF::sleep(difference);
index 9416c76..7dfb4a7 100644 (file)
@@ -43,11 +43,18 @@ class VM;
 
 namespace WebCore {
 
+enum ResourceUsageCollectionMode {
+    None = 0,
+    CPU = 1 << 0,
+    Memory = 1 << 1,
+    All = CPU | Memory,
+};
+
 class ResourceUsageThread {
     WTF_MAKE_NONCOPYABLE(ResourceUsageThread);
 
 public:
-    static void addObserver(void* key, std::function<void (const ResourceUsageData&)>);
+    static void addObserver(void* key, ResourceUsageCollectionMode, std::function<void (const ResourceUsageData&)>);
     static void removeObserver(void* key);
 
 private:
@@ -58,14 +65,19 @@ private:
     void waitUntilObservers();
     void notifyObservers(ResourceUsageData&&);
 
+    void recomputeCollectionMode();
+
     void createThreadIfNeeded();
     void threadBody();
-    void platformThreadBody(JSC::VM*, ResourceUsageData&);
+
+    void platformCollectCPUData(JSC::VM*, ResourceUsageData&);
+    void platformCollectMemoryData(JSC::VM*, ResourceUsageData&);
 
     RefPtr<Thread> m_thread;
     Lock m_lock;
     Condition m_condition;
-    HashMap<void*, std::function<void (const ResourceUsageData&)>> m_observers;
+    HashMap<void*, std::pair<ResourceUsageCollectionMode, std::function<void (const ResourceUsageData&)>>> m_observers;
+    ResourceUsageCollectionMode m_collectionMode { None };
 
     // Platforms may need to access some data from the common VM.
     // They should ensure their use of the VM is thread safe.
index e03e06f..4b5c631 100644 (file)
@@ -229,7 +229,7 @@ void ResourceUsageOverlay::platformInitialize()
 
     overlay().layer().setContentsToPlatformLayer(m_layer.get(), GraphicsLayer::ContentsLayerPurpose::None);
 
-    ResourceUsageThread::addObserver(this, [this] (const ResourceUsageData& data) {
+    ResourceUsageThread::addObserver(this, All, [this] (const ResourceUsageData& data) {
         appendDataToHistory(data);
 
         // FIXME: It shouldn't be necessary to update the bounds on every single thread loop iteration,
index 698fb8d..e644abe 100644 (file)
@@ -205,10 +205,13 @@ static unsigned categoryForVMTag(unsigned tag)
     }
 }
 
-void ResourceUsageThread::platformThreadBody(JSC::VM* vm, ResourceUsageData& data)
+void ResourceUsageThread::platformCollectCPUData(JSC::VM*, ResourceUsageData& data)
 {
     data.cpu = cpuUsage();
+}
 
+void ResourceUsageThread::platformCollectMemoryData(JSC::VM* vm, ResourceUsageData& data)
+{
     auto tags = pagesPerVMTag();
     std::array<TagInfo, MemoryCategory::NumberOfCategories> pagesPerCategory;
     size_t totalDirtyPages = 0;
@@ -248,9 +251,8 @@ void ResourceUsageThread::platformThreadBody(JSC::VM* vm, ResourceUsageData& dat
 
     data.totalExternalSize = currentGCOwnedExternal;
 
-    auto now = MonotonicTime::now();
-    data.timeOfNextEdenCollection = now + vm->heap.edenActivityCallback()->timeUntilFire().valueOr(Seconds(std::numeric_limits<double>::infinity()));
-    data.timeOfNextFullCollection = now + vm->heap.fullActivityCallback()->timeUntilFire().valueOr(Seconds(std::numeric_limits<double>::infinity()));
+    data.timeOfNextEdenCollection = data.timestamp + vm->heap.edenActivityCallback()->timeUntilFire().valueOr(Seconds(std::numeric_limits<double>::infinity()));
+    data.timeOfNextFullCollection = data.timestamp + vm->heap.fullActivityCallback()->timeUntilFire().valueOr(Seconds(std::numeric_limits<double>::infinity()));
 }
 
 }
index c3c87fd..e8a4bc0 100644 (file)
@@ -139,7 +139,7 @@ void ResourceUsageOverlay::platformInitialize()
     m_paintLayer->setDrawsContent(true);
     overlay().layer().addChild(*m_paintLayer);
 
-    ResourceUsageThread::addObserver(this, [this] (const ResourceUsageData& data) {
+    ResourceUsageThread::addObserver(this, All, [this] (const ResourceUsageData& data) {
         gData = data;
         m_paintLayer->setNeedsDisplay();
     });
index 1562a08..f76f15a 100644 (file)
@@ -150,10 +150,13 @@ static float cpuUsage()
     return clampTo<float>(usage, 0, 100);
 }
 
-void ResourceUsageThread::platformThreadBody(JSC::VM* vm, ResourceUsageData& data)
+void ResourceUsageThread::platformCollectCPUData(JSC::VM*, ResourceUsageData& data)
 {
     data.cpu = cpuUsage();
+}
 
+void ResourceUsageThread::platformCollectMemoryData(JSC::VM* vm, ResourceUsageData& data)
+{
     ProcessMemoryStatus memoryStatus;
     currentProcessMemoryStatus(memoryStatus);
     data.totalDirtySize = memoryStatus.resident - memoryStatus.shared;
@@ -169,9 +172,8 @@ void ResourceUsageThread::platformThreadBody(JSC::VM* vm, ResourceUsageData& dat
 
     data.totalExternalSize = currentGCOwnedExternal;
 
-    auto now = MonotonicTime::now();
-    data.timeOfNextEdenCollection = now + vm->heap.edenActivityCallback()->timeUntilFire().valueOr(Seconds(std::numeric_limits<double>::infinity()));
-    data.timeOfNextFullCollection = now + vm->heap.fullActivityCallback()->timeUntilFire().valueOr(Seconds(std::numeric_limits<double>::infinity()));
+    data.timeOfNextEdenCollection = data.timestamp + vm->heap.edenActivityCallback()->timeUntilFire().valueOr(Seconds(std::numeric_limits<double>::infinity()));
+    data.timeOfNextFullCollection = data.timestamp + vm->heap.fullActivityCallback()->timeUntilFire().valueOr(Seconds(std::numeric_limits<double>::infinity()));
 }
 
 } // namespace WebCore
index 44053a8..0e2243a 100644 (file)
@@ -1,3 +1,165 @@
+2019-01-24  Joseph Pecoraro  <pecoraro@apple.com>
+
+        Web Inspector: CPU Usage Timeline
+        https://bugs.webkit.org/show_bug.cgi?id=193730
+        <rdar://problem/46797201>
+
+        Reviewed by Devin Rousso.
+
+        CPU Usage is gathered in the backend twice a second, the frequency of the
+        ResourceUsageThread in WebCore. The frontend displays cpu usage in a few
+        ways in the Timeline.
+
+        We use a column chart in the timeline overview to display the frequency and
+        relative distance of samples. This helps show if the samples were close
+        together or far apart, which indicates how meaningful they will be at a
+        particular scale.
+
+        We use a line chart in the timeline detail view which will be easier to see
+        the changes over a particular time range selection.
+
+        * Localizations/en.lproj/localizedStrings.js:
+        New strings.
+
+        * UserInterface/Main.html:
+        * UserInterface/Base/Main.js:
+        (WI.loaded):
+        * UserInterface/Test.html:
+        * UserInterface/Test/Test.js:
+        (WI.loaded):
+        * UserInterface/Protocol/CPUProfilerObserver.js:
+        (WI.CPUProfilerObserver.prototype.trackingStart):
+        (WI.CPUProfilerObserver.prototype.trackingUpdate):
+        (WI.CPUProfilerObserver.prototype.trackingComplete):
+        (WI.CPUProfilerObserver):
+        New files and default registration.
+
+        * UserInterface/Protocol/Target.js:
+        (WI.Target.prototype.get CPUProfilerAgent):
+        New Agent.
+
+        * UserInterface/Controllers/TimelineManager.js:
+        (WI.TimelineManager.availableTimelineTypes):
+        (WI.TimelineManager.prototype.cpuProfilerTrackingStarted):
+        (WI.TimelineManager.prototype.cpuProfilerTrackingUpdated):
+        (WI.TimelineManager.prototype.cpuProfilerTrackingCompleted):
+        (WI.TimelineManager.prototype._updateAutoCaptureInstruments):
+        (WI.TimelineManager.prototype.memoryTrackingStart): Renamed.
+        (WI.TimelineManager.prototype.memoryTrackingUpdate): Renamed.
+        (WI.TimelineManager.prototype.memoryTrackingComplete): Renamed.
+        * UserInterface/Models/CPUInstrument.js:
+        (WI.CPUInstrument):
+        (WI.CPUInstrument.supported):
+        (WI.CPUInstrument.prototype.get timelineRecordType):
+        (WI.CPUInstrument.prototype.startInstrumentation):
+        (WI.CPUInstrument.prototype.stopInstrumentation):
+        * UserInterface/Models/CPUTimelineRecord.js:
+        (WI.CPUTimelineRecord):
+        (WI.CPUTimelineRecord.prototype.get timestamp):
+        (WI.CPUTimelineRecord.prototype.get usage):
+        * UserInterface/Models/Instrument.js:
+        (WI.Instrument.createForTimelineType):
+        * UserInterface/Models/TimelineRecord.js:
+        * UserInterface/Models/TimelineRecording.js:
+        (WI.TimelineRecording.prototype.addRecord):
+        Expose a new CPU instrument and timeline.
+
+        * UserInterface/Views/ColumnChart.js: Added.
+        (WI.ColumnChart):
+        (WI.ColumnChart.prototype.get element):
+        (WI.ColumnChart.prototype.get bars):
+        (WI.ColumnChart.prototype.get size):
+        (WI.ColumnChart.prototype.set size):
+        (WI.ColumnChart.prototype.addBar):
+        (WI.ColumnChart.prototype.clear):
+        (WI.ColumnChart.prototype.needsLayout):
+        (WI.ColumnChart.prototype.updateLayout):
+        View that will draw vertical bars with independent widths.
+        This is meant to be used similiar to WI.LineChart.
+
+        * UserInterface/Images/CPUInstrument.svg: Added.
+        * UserInterface/Views/Variables.css:
+        (:root):
+        CPU timeline colors and icon.
+
+        * UserInterface/Views/CPUTimelineOverviewGraph.css:
+        (body .sidebar > .panel.navigation.timeline > .timelines-content li.item.cpu,):
+        (.timeline-overview-graph.cpu):
+        (.timeline-overview-graph.cpu > .legend):
+        (body[dir=ltr] .timeline-overview-graph.cpu > .legend):
+        (body[dir=rtl] .timeline-overview-graph.cpu > .legend):
+        (.timeline-overview-graph:nth-child(even) > .legend):
+        (body[dir=rtl] .timeline-overview-graph.cpu > .bar-chart):
+        (.timeline-overview-graph.cpu > .bar-chart > svg > g > rect):
+        * UserInterface/Views/CPUTimelineOverviewGraph.js: Added.
+        (WI.CPUTimelineOverviewGraph):
+        (WI.CPUTimelineOverviewGraph.prototype.get height):
+        (WI.CPUTimelineOverviewGraph.prototype.reset):
+        (WI.CPUTimelineOverviewGraph.prototype.layout.xScale):
+        (WI.CPUTimelineOverviewGraph.prototype.layout.yScale):
+        (WI.CPUTimelineOverviewGraph.prototype.layout.yScaleForRecord):
+        (WI.CPUTimelineOverviewGraph.prototype.layout):
+        (WI.CPUTimelineOverviewGraph.prototype._updateLegend):
+        (WI.CPUTimelineOverviewGraph.prototype._cpuTimelineRecordAdded):
+        * UserInterface/Views/CPUTimelineView.css:
+        (.timeline-view.cpu):
+        (.timeline-view.cpu > .content):
+        (.timeline-view.cpu > .content .subtitle):
+        (.timeline-view.cpu > .content > .details):
+        (.timeline-view.cpu > .content > .details > .timeline-ruler):
+        (body[dir=ltr] .timeline-view.cpu > .content > .details > .timeline-ruler):
+        (body[dir=rtl] .timeline-view.cpu > .content > .details > .timeline-ruler):
+        (.timeline-view.cpu > .content > .details > .subtitle):
+        (.cpu-usage-view .line-chart > svg > path):
+        (.timeline-view.cpu .legend > .row > .swatch.current):
+        * UserInterface/Views/CPUTimelineView.js: Added.
+        (WI.CPUTimelineView):
+        (WI.CPUTimelineView.prototype.shown):
+        (WI.CPUTimelineView.prototype.hidden):
+        (WI.CPUTimelineView.prototype.closed):
+        (WI.CPUTimelineView.prototype.reset):
+        (WI.CPUTimelineView.prototype.get scrollableElements):
+        (WI.CPUTimelineView.prototype.get showsFilterBar):
+        (WI.CPUTimelineView.prototype.layout.layoutView):
+        (WI.CPUTimelineView.prototype.layout.xScale):
+        (WI.CPUTimelineView.prototype.layout.yScale):
+        (WI.CPUTimelineView.prototype.layout):
+        (WI.CPUTimelineView.prototype._cpuTimelineRecordAdded):
+        * UserInterface/Views/CPUUsageView.css:
+        (.cpu-usage-view):
+        (.cpu-usage-view > .details):
+        (body[dir=ltr] .cpu-usage-view > .details):
+        (body[dir=rtl] .cpu-usage-view > .details):
+        (.cpu-usage-view > .graph):
+        (body[dir=rtl] .cpu-usage-view > .graph):
+        * UserInterface/Views/CPUUsageView.js:
+        (WI.CPUUsageView):
+        (WI.CPUUsageView.prototype.get element):
+        (WI.CPUUsageView.prototype.clear):
+        (WI.CPUUsageView.prototype.layoutWithDataPoints):
+        (WI.CPUUsageView.prototype._updateDetails):
+        * UserInterface/Views/ContentView.js:
+        (WI.ContentView.createFromRepresentedObject):
+        * UserInterface/Views/TimelineIcons.css:
+        (.cpu-icon .icon):
+        * UserInterface/Views/TimelineOverviewGraph.js:
+        (WI.TimelineOverviewGraph.createForTimeline):
+        * UserInterface/Views/TimelineTabContentView.js:
+        (WI.TimelineTabContentView.displayNameForTimelineType):
+        (WI.TimelineTabContentView.iconClassNameForTimelineType):
+        (WI.TimelineTabContentView.genericClassNameForTimelineType):
+        (WI.TimelineTabContentView.iconClassNameForRecord):
+        (WI.TimelineTabContentView.displayNameForRecord):
+        Timeline views for CPU usage.
+
+        * UserInterface/Views/MemoryCategoryView.js:
+        (WI.MemoryCategoryView):
+        * UserInterface/Views/MemoryTimelineView.js:
+        (WI.MemoryTimelineView.createChartContainer):
+        (WI.MemoryTimelineView):
+        (WI.MemoryTimelineView.prototype._clearMaxComparisonLegend):
+        Minor updates to style and comments.
+
 2019-01-23  Nikita Vasilyev  <nvasilyev@apple.com>
 
         Web Inspector: Refactor WI.CSSStyleDeclaration.prototype.update
index a6ac3b2..a2f3c6d 100644 (file)
@@ -139,6 +139,7 @@ localizedStrings["Auto Increment"] = "Auto Increment";
 localizedStrings["Automatically continue after evaluating"] = "Automatically continue after evaluating";
 localizedStrings["Available Style Sheets"] = "Available Style Sheets";
 localizedStrings["Average Time"] = "Average Time";
+localizedStrings["Average: %s"] = "Average: %s";
 localizedStrings["Back (%s)"] = "Back (%s)";
 localizedStrings["Backtrace"] = "Backtrace";
 localizedStrings["Basic"] = "Basic";
@@ -162,6 +163,8 @@ localizedStrings["Busy"] = "Busy";
 localizedStrings["Byte Range %s\u2013%s"] = "Byte Range %s\u2013%s";
 localizedStrings["Bytes Received"] = "Bytes Received";
 localizedStrings["Bytes Sent"] = "Bytes Sent";
+localizedStrings["CPU"] = "CPU";
+localizedStrings["CPU Usage"] = "CPU Usage";
 localizedStrings["CSP Hash"] = "CSP Hash";
 localizedStrings["CSS"] = "CSS";
 localizedStrings["CSS Canvas"] = "CSS Canvas";
@@ -578,6 +581,7 @@ localizedStrings["Mass"] = "Mass";
 localizedStrings["Matching"] = "Matching";
 localizedStrings["Max Comparison"] = "Max Comparison";
 localizedStrings["Maximum"] = "Maximum";
+localizedStrings["Maximum CPU Usage: %s"] = "Maximum CPU Usage: %s";
 localizedStrings["Maximum Size: %s"] = "Maximum Size: %s";
 localizedStrings["Maximum maximum memory size in this recording"] = "Maximum maximum memory size in this recording";
 localizedStrings["Media"] = "Media";
index 81b06dd..71cb8ed 100644 (file)
@@ -75,6 +75,8 @@ WI.loaded = function()
         InspectorBackend.registerDOMStorageDispatcher(new WI.DOMStorageObserver);
     if (InspectorBackend.registerApplicationCacheDispatcher)
         InspectorBackend.registerApplicationCacheDispatcher(new WI.ApplicationCacheObserver);
+    if (InspectorBackend.registerCPUProfilerDispatcher)
+        InspectorBackend.registerCPUProfilerDispatcher(new WI.CPUProfilerObserver);
     if (InspectorBackend.registerScriptProfilerDispatcher)
         InspectorBackend.registerScriptProfilerDispatcher(new WI.ScriptProfilerObserver);
     if (InspectorBackend.registerTimelineDispatcher)
index 61b745d..259205e 100644 (file)
@@ -117,6 +117,9 @@ WI.TimelineManager = class TimelineManager extends WI.Object
         if (WI.sharedApp.debuggableType === WI.DebuggableType.JavaScript || WI.sharedApp.debuggableType === WI.DebuggableType.ServiceWorker)
             return types;
 
+        if (WI.CPUInstrument.supported())
+            types.push(WI.TimelineRecord.Type.CPU);
+
         if (WI.MemoryInstrument.supported())
             types.push(WI.TimelineRecord.Type.Memory);
 
@@ -449,14 +452,36 @@ WI.TimelineManager = class TimelineManager extends WI.Object
         this._stopAutoRecordingSoon();
     }
 
-    memoryTrackingStart(timestamp)
+    cpuProfilerTrackingStarted(timestamp)
+    {
+        // Called from WI.CPUProfilerObserver.
+
+        this.capturingStarted(timestamp);
+    }
+
+    cpuProfilerTrackingUpdated(event)
+    {
+        // Called from WI.CPUProfilerObserver.
+
+        if (!this._isCapturing)
+            return;
+
+        this._addRecord(new WI.CPUTimelineRecord(event.timestamp, event.usage));
+    }
+
+    cpuProfilerTrackingCompleted()
+    {
+        // Called from WI.CPUProfilerObserver.
+    }
+
+    memoryTrackingStarted(timestamp)
     {
         // Called from WI.MemoryObserver.
 
         this.capturingStarted(timestamp);
     }
 
-    memoryTrackingUpdate(event)
+    memoryTrackingUpdated(event)
     {
         // Called from WI.MemoryObserver.
 
@@ -466,7 +491,7 @@ WI.TimelineManager = class TimelineManager extends WI.Object
         this._addRecord(new WI.MemoryTimelineRecord(event.timestamp, event.categories));
     }
 
-    memoryTrackingComplete()
+    memoryTrackingCompleted()
     {
         // Called from WI.MemoryObserver.
     }
@@ -1141,6 +1166,9 @@ WI.TimelineManager = class TimelineManager extends WI.Object
                 case WI.TimelineRecord.Type.Media:
                     instrumentSet.add(target.TimelineAgent.Instrument.Timeline);
                     break;
+                case WI.TimelineRecord.Type.CPU:
+                    instrumentSet.add(target.TimelineAgent.Instrument.CPU);
+                    break;
                 case WI.TimelineRecord.Type.Memory:
                     instrumentSet.add(target.TimelineAgent.Instrument.Memory);
                     break;
index 4c826eb..7ae3716 100644 (file)
@@ -37,7 +37,7 @@
 .uncaught-exception-sheet {
     min-width: 400px;
     padding: 50px 0;
-    font-family: '-webkit-system-font';
+    font-family: -webkit-system-font, sans-serif;
     font-size: 11pt;
     color: hsl(0, 0%, 40%);
 
diff --git a/Source/WebInspectorUI/UserInterface/Images/CPUInstrument.svg b/Source/WebInspectorUI/UserInterface/Images/CPUInstrument.svg
new file mode 100644 (file)
index 0000000..319ec92
--- /dev/null
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright © 2019 Apple Inc. All rights reserved. -->
+<svg xmlns="http://www.w3.org/2000/svg" id="root" version="1.1" viewBox="0 0 16 16">
+    <circle stroke="hsl(118, 33%, 42%)" fill="hsl(118, 43%, 55%)" cx="8" cy="8" r="7.5"/>
+    <rect stroke="hsl(118, 33%, 42%)" stroke-width="2" x="4" y="4" width="2" height="8" rx="0.25"/>
+    <rect stroke="hsl(118, 33%, 42%)" stroke-width="2" x="7" y="4" width="2" height="8" rx="0.25"/>
+    <rect stroke="hsl(118, 33%, 42%)" stroke-width="2" x="10" y="4" width="2" height="8" rx="0.25"/>
+    <rect fill="white" x="4" y="4" width="2" height="8" rx="0.25"/>
+    <rect fill="white" x="7" y="4" width="2" height="8" rx="0.25"/>
+    <rect fill="white" x="10" y="4" width="2" height="8" rx="0.25"/>
+    <rect stroke="hsl(118, 33%, 42%)" stroke-width="2" x="3" y="6" width="10" height="4" rx="0.25"/>
+    <rect fill="white" x="3" y="6" width="10" height="4" rx="0.25"/>
+</svg>
index c55125b..33c5cbe 100644 (file)
@@ -43,6 +43,9 @@
     <link rel="stylesheet" href="Views/BreakpointTreeElement.css">
     <link rel="stylesheet" href="Views/ButtonNavigationItem.css">
     <link rel="stylesheet" href="Views/ButtonToolbarItem.css">
+    <link rel="stylesheet" href="Views/CPUTimelineOverviewGraph.css">
+    <link rel="stylesheet" href="Views/CPUTimelineView.css">
+    <link rel="stylesheet" href="Views/CPUUsageView.css">
     <link rel="stylesheet" href="Views/CallFrameIcons.css">
     <link rel="stylesheet" href="Views/CallFrameTreeElement.css">
     <link rel="stylesheet" href="Views/CallFrameView.css">
     <link rel="stylesheet" href="Views/DetailsSection.css">
     <link rel="stylesheet" href="Views/DividerNavigationItem.css">
     <link rel="stylesheet" href="Views/Editing.css">
+    <link rel="stylesheet" href="Views/ErrorObjectView.css">
     <link rel="stylesheet" href="Views/EventBreakpointPopover.css">
     <link rel="stylesheet" href="Views/EventBreakpointTreeElement.css">
     <link rel="stylesheet" href="Views/EventListenerSectionGroup.css">
-    <link rel="stylesheet" href="Views/ErrorObjectView.css">
     <link rel="stylesheet" href="Views/FilterBar.css">
     <link rel="stylesheet" href="Views/FindBanner.css">
     <link rel="stylesheet" href="Views/FlexibleSpaceNavigationItem.css">
     <script src="Protocol/WorkerTarget.js"></script>
 
     <script src="Protocol/ApplicationCacheObserver.js"></script>
+    <script src="Protocol/CPUProfilerObserver.js"></script>
     <script src="Protocol/CSSObserver.js"></script>
     <script src="Protocol/CanvasObserver.js"></script>
     <script src="Protocol/ConsoleObserver.js"></script>
     <script src="Models/BackForwardEntry.js"></script>
     <script src="Models/Branch.js"></script>
     <script src="Models/Breakpoint.js"></script>
-    <script src="Models/CallingContextTree.js"></script>
-    <script src="Models/CallingContextTreeNode.js"></script>
+    <script src="Models/CPUInstrument.js"></script>
+    <script src="Models/CPUTimelineRecord.js"></script>
     <script src="Models/CSSCompletions.js"></script>
     <script src="Models/CSSKeywordCompletions.js"></script>
     <script src="Models/CSSMedia.js"></script>
     <script src="Models/CSSStyleDeclaration.js"></script>
     <script src="Models/CSSStyleSheet.js"></script>
     <script src="Models/CallFrame.js"></script>
+    <script src="Models/CallingContextTree.js"></script>
+    <script src="Models/CallingContextTreeNode.js"></script>
     <script src="Models/Canvas.js"></script>
     <script src="Models/Collection.js"></script>
     <script src="Models/CollectionEntry.js"></script>
     <script src="Views/SettingsGroup.js"></script>
     <script src="Views/SettingsView.js"></script>
 
+    <script src="Views/AuditTestContentView.js"></script>
     <script src="Views/CollectionContentView.js"></script>
 
     <script src="Views/ActivateButtonNavigationItem.js"></script>
     <script src="Views/ApplicationCacheFrameTreeElement.js"></script>
     <script src="Views/ApplicationCacheManifestTreeElement.js"></script>
     <script src="Views/AuditNavigationSidebarPanel.js"></script>
-    <script src="Views/AuditTestContentView.js"></script>
     <script src="Views/AuditTestCaseContentView.js"></script>
     <script src="Views/AuditTestGroupContentView.js"></script>
     <script src="Views/AuditTreeElement.js"></script>
     <script src="Views/BreakpointActionView.js"></script>
     <script src="Views/BreakpointTreeElement.js"></script>
     <script src="Views/ButtonToolbarItem.js"></script>
+    <script src="Views/CPUTimelineOverviewGraph.js"></script>
+    <script src="Views/CPUTimelineView.js"></script>
+    <script src="Views/CPUUsageView.js"></script>
     <script src="Views/CSSStyleSheetTreeElement.js"></script>
     <script src="Views/CallFrameTreeElement.js"></script>
     <script src="Views/CallFrameView.js"></script>
     <script src="Views/CodeMirrorTextMarkers.js"></script>
     <script src="Views/ColorPicker.js"></script>
     <script src="Views/ColorWheel.js"></script>
+    <script src="Views/ColumnChart.js"></script>
     <script src="Views/CompletionSuggestionsView.js"></script>
     <script src="Views/ComputedStyleDetailsPanel.js"></script>
     <script src="Views/ComputedStyleSection.js"></script>
diff --git a/Source/WebInspectorUI/UserInterface/Models/CPUInstrument.js b/Source/WebInspectorUI/UserInterface/Models/CPUInstrument.js
new file mode 100644 (file)
index 0000000..dfcc6e7
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+WI.CPUInstrument = class CPUInstrument extends WI.Instrument
+{
+    constructor()
+    {
+        super();
+
+        console.assert(WI.CPUInstrument.supported());
+    }
+
+    // Static
+
+    static supported()
+    {
+        // COMPATIBILITY (iOS 12): CPUProfiler domain did not exist.
+        return InspectorBackend.domains.CPUProfiler;
+    }
+
+    // Protected
+
+    get timelineRecordType()
+    {
+        return WI.TimelineRecord.Type.CPU;
+    }
+
+    startInstrumentation(initiatedByBackend)
+    {
+        if (!initiatedByBackend)
+            CPUProfilerAgent.startTracking();
+    }
+
+    stopInstrumentation(initiatedByBackend)
+    {
+        if (!initiatedByBackend)
+            CPUProfilerAgent.stopTracking();
+    }
+};
diff --git a/Source/WebInspectorUI/UserInterface/Models/CPUTimelineRecord.js b/Source/WebInspectorUI/UserInterface/Models/CPUTimelineRecord.js
new file mode 100644 (file)
index 0000000..f000284
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2016 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+WI.CPUTimelineRecord = class CPUTimelineRecord extends WI.TimelineRecord
+{
+    constructor(timestamp, usage)
+    {
+        super(WI.TimelineRecord.Type.CPU, timestamp, timestamp);
+
+        console.assert(typeof timestamp === "number");
+        console.assert(typeof usage === "number");
+        console.assert(usage >= 0);
+
+        this._timestamp = timestamp;
+        this._usage = usage;
+    }
+
+    // Public
+
+    get timestamp() { return this._timestamp; }
+    get usage() { return this._usage; }
+};
index f12c3af..4c12d36 100644 (file)
@@ -38,6 +38,8 @@ WI.Instrument = class Instrument
             return new WI.ScriptInstrument;
         case WI.TimelineRecord.Type.RenderingFrame:
             return new WI.FPSInstrument;
+        case WI.TimelineRecord.Type.CPU:
+            return new WI.CPUInstrument;
         case WI.TimelineRecord.Type.Memory:
             return new WI.MemoryInstrument;
         case WI.TimelineRecord.Type.HeapAllocations:
index 5c5e78b..bd4a995 100644 (file)
@@ -158,6 +158,7 @@ WI.TimelineRecord.Type = {
     Layout: "timeline-record-type-layout",
     Script: "timeline-record-type-script",
     RenderingFrame: "timeline-record-type-rendering-frame",
+    CPU: "timeline-record-type-cpu",
     Memory: "timeline-record-type-memory",
     HeapAllocations: "timeline-record-type-heap-allocations",
     Media: "timeline-record-type-media",
index fc7587e..9ae7638 100644 (file)
@@ -211,6 +211,7 @@ WI.TimelineRecording = class TimelineRecording extends WI.Object
         // Some records don't have source code timelines.
         if (record.type === WI.TimelineRecord.Type.Network
             || record.type === WI.TimelineRecord.Type.RenderingFrame
+            || record.type === WI.TimelineRecord.Type.CPU
             || record.type === WI.TimelineRecord.Type.Memory
             || record.type === WI.TimelineRecord.Type.HeapAllocations
             || record.type === WI.TimelineRecord.Type.Media)
diff --git a/Source/WebInspectorUI/UserInterface/Protocol/CPUProfilerObserver.js b/Source/WebInspectorUI/UserInterface/Protocol/CPUProfilerObserver.js
new file mode 100644 (file)
index 0000000..49a2345
--- /dev/null
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+WI.CPUProfilerObserver = class CPUProfilerObserver
+{
+    // Events defined by the "CPUProfiler" domain.
+
+    trackingStart(timestamp)
+    {
+        WI.timelineManager.cpuProfilerTrackingStarted(timestamp);
+    }
+
+    trackingUpdate(event)
+    {
+        WI.timelineManager.cpuProfilerTrackingUpdated(event);
+    }
+
+    trackingComplete(samples)
+    {
+        WI.timelineManager.cpuProfilerTrackingCompleted(samples);
+    }
+};
index 7e1d326..7224de2 100644 (file)
@@ -34,16 +34,16 @@ WI.MemoryObserver = class MemoryObserver
 
     trackingStart(timestamp)
     {
-        WI.timelineManager.memoryTrackingStart(timestamp);
+        WI.timelineManager.memoryTrackingStarted(timestamp);
     }
 
     trackingUpdate(event)
     {
-        WI.timelineManager.memoryTrackingUpdate(event);
+        WI.timelineManager.memoryTrackingUpdated(event);
     }
 
     trackingComplete()
     {
-        WI.timelineManager.memoryTrackingComplete();
+        WI.timelineManager.memoryTrackingCompleted();
     }
 };
index b0ea5ea..d1687e2 100644 (file)
@@ -111,6 +111,7 @@ WI.Target = class Target extends WI.Object
 
     get AuditAgent() { return this._agents.Audit; }
     get ApplicationCacheAgent() { return this._agents.ApplicationCache; }
+    get CPUProfilerAgent() { return this._agents.CPUProfiler; }
     get CSSAgent() { return this._agents.CSS; }
     get CanvasAgent() { return this._agents.Canvas; }
     get ConsoleAgent() { return this._agents.Console; }
index ee4282b..06a0d84 100644 (file)
@@ -80,6 +80,7 @@
     <script src="Protocol/WorkerTarget.js"></script>
 
     <script src="Protocol/InspectorObserver.js"></script>
+    <script src="Protocol/CPUProfilerObserver.js"></script>
     <script src="Protocol/CSSObserver.js"></script>
     <script src="Protocol/CanvasObserver.js"></script>
     <script src="Protocol/ConsoleObserver.js"></script>
     <script src="Models/LocalScript.js"></script>
 
     <script src="Models/Breakpoint.js"></script>
+    <script src="Models/CPUInstrument.js"></script>
+    <script src="Models/CPUTimelineRecord.js"></script>
     <script src="Models/CSSCompletions.js"></script>
     <script src="Models/CSSKeywordCompletions.js"></script>
     <script src="Models/CSSMedia.js"></script>
index 195fbc2..bdcca1b 100644 (file)
@@ -38,6 +38,7 @@ WI.loaded = function()
     InspectorBackend.registerMemoryDispatcher(new WI.MemoryObserver);
     InspectorBackend.registerDOMStorageDispatcher(new WI.DOMStorageObserver);
     InspectorBackend.registerScriptProfilerDispatcher(new WI.ScriptProfilerObserver);
+    InspectorBackend.registerCPUProfilerDispatcher(new WI.CPUProfilerObserver);
     InspectorBackend.registerTimelineDispatcher(new WI.TimelineObserver);
     InspectorBackend.registerCSSDispatcher(new WI.CSSObserver);
     InspectorBackend.registerLayerTreeDispatcher(new WI.LayerTreeObserver);
diff --git a/Source/WebInspectorUI/UserInterface/Views/CPUTimelineOverviewGraph.css b/Source/WebInspectorUI/UserInterface/Views/CPUTimelineOverviewGraph.css
new file mode 100644 (file)
index 0000000..aaeb296
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+body .sidebar > .panel.navigation.timeline > .timelines-content li.item.cpu,
+body .timeline-overview > .graphs-container > .timeline-overview-graph.cpu {
+    height: 72px;
+}
+
+.timeline-overview-graph.cpu {
+    position: relative;
+}
+
+.timeline-overview-graph.cpu > .legend {
+    position: absolute;
+    top: 0;
+    padding: 2px;
+    font-size: 8px;
+    color: hsl(0, 0%, 50%);
+    background-color: white;
+    pointer-events: none;
+
+    --cpu-timeline-overview-graph-legend-offset-end: 0;
+}
+
+body[dir=ltr] .timeline-overview-graph.cpu > .legend {
+    right: var(--cpu-timeline-overview-graph-legend-offset-end);
+}
+
+body[dir=rtl] .timeline-overview-graph.cpu > .legend {
+    left: var(--cpu-timeline-overview-graph-legend-offset-end);
+}
+
+.timeline-overview-graph:nth-child(even) > .legend {
+    background-color: hsl(0, 0%, 96%);
+}
+
+body[dir=rtl] .timeline-overview-graph.cpu > .bar-chart {
+    transform: scaleX(-1);
+}
+
+.timeline-overview-graph.cpu > .bar-chart > svg > rect {
+    stroke: var(--cpu-stroke-color);
+    fill: var(--cpu-fill-color);
+}
diff --git a/Source/WebInspectorUI/UserInterface/Views/CPUTimelineOverviewGraph.js b/Source/WebInspectorUI/UserInterface/Views/CPUTimelineOverviewGraph.js
new file mode 100644 (file)
index 0000000..3065710
--- /dev/null
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+WI.CPUTimelineOverviewGraph = class CPUTimelineOverviewGraph extends WI.TimelineOverviewGraph
+{
+    constructor(timeline, timelineOverview)
+    {
+        console.assert(timeline instanceof WI.Timeline);
+        console.assert(timeline.type === WI.TimelineRecord.Type.CPU, timeline);
+
+        super(timelineOverview);
+
+        this.element.classList.add("cpu");
+
+        this._cpuTimeline = timeline;
+        this._cpuTimeline.addEventListener(WI.Timeline.Event.RecordAdded, this._cpuTimelineRecordAdded, this);
+
+        let size = new WI.Size(0, this.height);
+        this._chart = new WI.ColumnChart(size);
+        this.element.appendChild(this._chart.element);
+
+        this._legendElement = this.element.appendChild(document.createElement("div"));
+        this._legendElement.classList.add("legend");
+
+        this.reset();
+    }
+
+    // Protected
+
+    get height()
+    {
+        return 72;
+    }
+
+    reset()
+    {
+        super.reset();
+
+        this._maxUsage = 0;
+        this._cachedMaxUsage = undefined;
+
+        this._updateLegend();
+        this._chart.clear();
+        this._chart.needsLayout();
+    }
+
+    layout()
+    {
+        if (!this.visible)
+            return;
+
+        this._updateLegend();
+        this._chart.clear();
+
+        let graphWidth = this.timelineOverview.scrollContainerWidth;
+        if (isNaN(graphWidth))
+            return;
+
+        if (this._chart.size.width !== graphWidth || this._chart.size.height !== this.height)
+            this._chart.size = new WI.Size(graphWidth, this.height);
+
+        let graphStartTime = this.startTime;
+        let visibleEndTime = Math.min(this.endTime, this.currentTime);
+        let secondsPerPixel = this.timelineOverview.secondsPerPixel;
+        let maxCapacity = Math.max(20, this._maxUsage * 1.05); // Add 5% for padding.
+
+        // 500ms. This matches the ResourceUsageThread sampling frequency in the backend.
+        const samplingRatePerSecond = 0.5;
+
+        function xScale(time) {
+            return (time - graphStartTime) / secondsPerPixel;
+        }
+
+        let height = this.height;
+        function yScale(size) {
+            return (size / maxCapacity) * height;
+        }
+
+        const includeRecordBeforeStart = true;
+        let visibleRecords = this._cpuTimeline.recordsInTimeRange(graphStartTime, visibleEndTime + (samplingRatePerSecond / 2), includeRecordBeforeStart);
+        if (!visibleRecords.length)
+            return;
+
+        function yScaleForRecord(record) {
+            return yScale(record.usage);
+        }
+
+        let intervalWidth = (samplingRatePerSecond / secondsPerPixel);
+        const minimumDisplayHeight = 2;
+
+        // Bars for each record.
+        for (let record of visibleRecords) {
+            let w = intervalWidth;
+            let h = Math.max(minimumDisplayHeight, yScale(record.usage));
+            let x = xScale(record.startTime - (samplingRatePerSecond / 2));
+            let y = height - h;
+            this._chart.addBar(x, y, w, h);
+        }
+
+        this._chart.updateLayout();
+    }
+
+    // Private
+
+    _updateLegend()
+    {
+        if (this._cachedMaxUsage === this._maxUsage)
+            return;
+
+        this._cachedMaxUsage = this._maxUsage;
+
+        if (!this._maxUsage) {
+            this._legendElement.hidden = true;
+            this._legendElement.textContent = "";
+        } else {
+            this._legendElement.hidden = false;
+            this._legendElement.textContent = WI.UIString("Maximum CPU Usage: %s").format(Number.percentageString(this._maxUsage / 100));
+        }
+    }
+
+    _cpuTimelineRecordAdded(event)
+    {
+        let cpuTimelineRecord = event.data.record;
+
+        this._maxUsage = Math.max(this._maxUsage, cpuTimelineRecord.usage);
+
+        this.needsLayout();
+    }
+};
diff --git a/Source/WebInspectorUI/UserInterface/Views/CPUTimelineView.css b/Source/WebInspectorUI/UserInterface/Views/CPUTimelineView.css
new file mode 100644 (file)
index 0000000..c5ab00d
--- /dev/null
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+.timeline-view.cpu {
+    overflow: scroll;
+}
+
+.timeline-view.cpu > .content {
+    width: 970px;
+    margin: 10px auto;
+}
+
+.timeline-view.cpu > .content .subtitle {
+    font-family: -webkit-system-font, sans-serif;
+    font-size: 14px;
+}
+
+.timeline-view.cpu > .content > .details {
+    position: relative;
+}
+
+.timeline-view.cpu > .content > .details > .timeline-ruler {
+    position: absolute;
+    top: 5px;
+    bottom: 0;
+    right: 0;
+    left: 0;
+
+    --cpu-timeline-view-details-timeline-ruler-offset-start: 150px;
+}
+
+body[dir=ltr] .timeline-view.cpu > .content > .details > .timeline-ruler {
+    left: var(--cpu-timeline-view-details-timeline-ruler-offset-start);
+}
+
+body[dir=rtl] .timeline-view.cpu > .content > .details > .timeline-ruler {
+    right: var(--cpu-timeline-view-details-timeline-ruler-offset-start);
+}
+
+.timeline-view.cpu > .content > .details > .subtitle {
+    padding: 0 10px 10px;
+    border-bottom: 1px solid var(--border-color);
+}
+
+.cpu-usage-view .line-chart > svg > path {
+    stroke: var(--cpu-stroke-color);
+    fill: var(--cpu-fill-color);
+}
+
+.timeline-view.cpu .legend > .row > .swatch.current {
+    border: 1px solid var(--cpu-max-comparison-stroke-color);
+    background: var(--cpu-max-comparison-fill-color);
+}
diff --git a/Source/WebInspectorUI/UserInterface/Views/CPUTimelineView.js b/Source/WebInspectorUI/UserInterface/Views/CPUTimelineView.js
new file mode 100644 (file)
index 0000000..ce48b38
--- /dev/null
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+WI.CPUTimelineView = class CPUTimelineView extends WI.TimelineView
+{
+    constructor(timeline, extraArguments)
+    {
+        console.assert(timeline.type === WI.TimelineRecord.Type.CPU, timeline);
+
+        super(timeline, extraArguments);
+
+        this._recording = extraArguments.recording;
+        this._maxUsage = -Infinity;
+
+        this.element.classList.add("cpu");
+
+        let contentElement = this.element.appendChild(document.createElement("div"));
+        contentElement.classList.add("content");
+
+        // FIXME: Overview with charts.
+
+        let detailsContainerElement = this._detailsContainerElement = contentElement.appendChild(document.createElement("div"));
+        detailsContainerElement.classList.add("details");
+
+        this._timelineRuler = new WI.TimelineRuler;
+        this.addSubview(this._timelineRuler);
+        detailsContainerElement.appendChild(this._timelineRuler.element);
+
+        let detailsSubtitleElement = detailsContainerElement.appendChild(document.createElement("div"));
+        detailsSubtitleElement.classList.add("subtitle");
+        detailsSubtitleElement.textContent = WI.UIString("CPU Usage");
+
+        this._cpuUsageView = new WI.CPUUsageView;
+        this._detailsContainerElement.appendChild(this._cpuUsageView.element);
+
+        timeline.addEventListener(WI.Timeline.Event.RecordAdded, this._cpuTimelineRecordAdded, this);
+    }
+
+    // Public
+
+    shown()
+    {
+        super.shown();
+
+        this._timelineRuler.updateLayout(WI.View.LayoutReason.Resize);
+    }
+
+    closed()
+    {
+        console.assert(this.representedObject instanceof WI.Timeline);
+        this.representedObject.removeEventListener(null, null, this);
+    }
+
+    reset()
+    {
+        super.reset();
+
+        this._maxUsage = -Infinity;
+
+        this._cpuUsageView.clear();
+    }
+
+    get scrollableElements()
+    {
+        return [this.element];
+    }
+
+    // Protected
+
+    get showsFilterBar() { return false; }
+
+    layout()
+    {
+        // Always update timeline ruler.
+        this._timelineRuler.zeroTime = this.zeroTime;
+        this._timelineRuler.startTime = this.startTime;
+        this._timelineRuler.endTime = this.endTime;
+
+        let graphStartTime = this.startTime;
+        let graphEndTime = this.endTime;
+        let visibleEndTime = Math.min(this.endTime, this.currentTime);
+
+        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;
+        let visibleRecords = this.representedObject.recordsInTimeRange(graphStartTime, visibleEndTime, includeRecordBeforeStart);
+        if (!visibleRecords.length)
+            return;
+
+        // Update total usage chart with the last record's data.
+        let lastRecord = visibleRecords.lastValue;
+
+        // FIXME: Left chart.
+        // FIXME: Right chart.
+
+        // FIXME: <https://webkit.org/b/153758> Web Inspector: Memory / CPU Timeline Views should be responsive / resizable
+        const cpuUsageViewWidth = 800;
+        const cpuUsageViewHeight = 150;
+
+        let secondsPerPixel = (graphEndTime - graphStartTime) / cpuUsageViewWidth;
+
+        let dataPoints = [];
+        let max = -Infinity;
+        let min = Infinity;
+        let average = 0;
+
+        for (let record of visibleRecords) {
+            let time = record.startTime;
+            let usage = record.usage;
+
+            if (discontinuities.length && discontinuities[0].endTime < time) {
+                let discontinuity = discontinuities.shift();
+                dataPoints.push({time: discontinuity.startTime, size: 0});
+                dataPoints.push({time: discontinuity.endTime, size: 0});
+                dataPoints.push({time: discontinuity.endTime, size: usage});
+            }
+
+            dataPoints.push({time, size: usage});
+            max = Math.max(max, usage);
+            min = Math.min(min, usage);
+            average += usage;
+        }
+
+        average /= visibleRecords.length;
+
+        // 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 layoutView(view, {dataPoints, min, max, average}) {
+            if (min === Infinity)
+                min = 0;
+            if (max === -Infinity)
+                max = 0;
+
+            // Zoom in to the top of each graph to accentuate small changes.
+            let graphMin = min * 0.95;
+            let graphMax = (max * 1.05) - graphMin;
+
+            function xScale(time) {
+                return (time - graphStartTime) / secondsPerPixel;
+            }
+            function yScale(size) {
+                return cpuUsageViewHeight - (((size - graphMin) / graphMax) * cpuUsageViewHeight);
+            }
+
+            view.layoutWithDataPoints(dataPoints, visibleEndTime, min, max, average, xScale, yScale);
+        }
+
+        layoutView(this._cpuUsageView, {dataPoints, min, max, average});
+    }
+
+    // Private
+
+    _cpuTimelineRecordAdded(event)
+    {
+        let cpuTimelineRecord = event.data.record;
+        console.assert(cpuTimelineRecord instanceof WI.CPUTimelineRecord);
+
+        this._maxUsage = Math.max(this._maxUsage, cpuTimelineRecord.usage);
+
+        if (cpuTimelineRecord.startTime >= this.startTime && cpuTimelineRecord.endTime <= this.endTime)
+            this.needsLayout();
+    }
+};
diff --git a/Source/WebInspectorUI/UserInterface/Views/CPUUsageView.css b/Source/WebInspectorUI/UserInterface/Views/CPUUsageView.css
new file mode 100644 (file)
index 0000000..1b137bc
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+.cpu-usage-view {
+    display: flex;
+    border-bottom: 1px solid var(--border-color);
+}
+
+.cpu-usage-view > .details {
+    width: 150px;
+    padding-top: 10px;
+    font-family: -webkit-system-font, sans-serif;
+    font-size: 12px;
+    color: hsl(0, 0%, 70%);
+    -webkit-padding-start: 15px;
+
+    --cpu-usage-view-details-border-end: 1px solid var(--border-color);
+}
+
+body[dir=ltr] .cpu-usage-view > .details {
+    border-right: var(--cpu-usage-view-details-border-end);
+}
+
+body[dir=rtl] .cpu-usage-view > .details {
+    border-left: var(--cpu-usage-view-details-border-end);
+}
+
+.cpu-usage-view > .graph {
+    position: relative;
+    bottom: -3px;
+}
+
+body[dir=rtl] .cpu-usage-view > .graph {
+    transform: scaleX(-1);
+}
diff --git a/Source/WebInspectorUI/UserInterface/Views/CPUUsageView.js b/Source/WebInspectorUI/UserInterface/Views/CPUUsageView.js
new file mode 100644 (file)
index 0000000..0360afc
--- /dev/null
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+WI.CPUUsageView = class CPUUsageView
+{
+    constructor()
+    {
+        this._element = document.createElement("div");
+        this._element.classList.add("cpu-usage-view");
+
+        this._detailsElement = this._element.appendChild(document.createElement("div"));
+        this._detailsElement.classList.add("details");
+
+        this._detailsAverageElement = this._detailsElement.appendChild(document.createElement("span"));        
+        this._detailsElement.appendChild(document.createElement("br"));
+        this._detailsMaxElement = this._detailsElement.appendChild(document.createElement("span"));
+        this._detailsElement.appendChild(document.createElement("br"));
+        this._detailsMinElement = this._detailsElement.appendChild(document.createElement("span"));
+        this._updateDetails(NaN, NaN);
+
+        this._graphElement = this._element.appendChild(document.createElement("div"));
+        this._graphElement.classList.add("graph");
+
+        // FIXME: <https://webkit.org/b/153758> Web Inspector: Memory / CPU Timeline Views should be responsive / resizable
+        let size = new WI.Size(800, 150);
+        this._chart = new WI.LineChart(size);
+        this._graphElement.appendChild(this._chart.element);
+    }
+
+    // Public
+
+    get element() { return this._element; }
+
+    clear()
+    {
+        this._cachedAverageSize = undefined;
+        this._cachedMinSize = undefined;
+        this._cachedMaxSize = undefined;
+        this._updateDetails(NaN, NaN);
+
+        this._chart.clear();
+        this._chart.needsLayout();
+    }
+
+    layoutWithDataPoints(dataPoints, lastTime, minSize, maxSize, averageSize, xScale, yScale)
+    {
+        console.assert(minSize >= 0);
+        console.assert(maxSize >= 0);
+        console.assert(minSize <= maxSize);
+
+        this._updateDetails(minSize, maxSize, averageSize);
+        this._chart.clear();
+
+        if (!dataPoints.length)
+            return;
+
+        // Ensure an empty graph is empty.
+        if (!maxSize)
+            return;
+
+        // Extend the first data point to the start so it doesn't look like we originate at zero size.
+        let firstX = 0;
+        let firstY = yScale(dataPoints[0].size);
+        this._chart.addPoint(firstX, firstY);
+
+        // Points for data points.
+        for (let dataPoint of dataPoints) {
+            let x = xScale(dataPoint.time);
+            let y = yScale(dataPoint.size);
+            this._chart.addPoint(x, y);
+        }
+
+        // Extend the last data point to the end time.
+        let lastDataPoint = dataPoints.lastValue;
+        let lastX = Math.floor(xScale(lastTime));
+        let lastY = yScale(lastDataPoint.size);
+        this._chart.addPoint(lastX, lastY);
+
+        this._chart.updateLayout();
+    }
+
+    // Private
+
+    _updateDetails(minSize, maxSize, averageSize)
+    {
+        if (this._cachedMinSize === minSize && this._cachedMaxSize === maxSize && this._cachedAverageSize === averageSize)
+            return;
+
+        this._cachedAverageSize = averageSize;
+        this._cachedMinSize = minSize;
+        this._cachedMaxSize = maxSize;
+
+        this._detailsAverageElement.textContent = WI.UIString("Average: %s").format(Number.isFinite(maxSize) ? Number.percentageString(averageSize / 100) : emDash);
+        this._detailsMaxElement.textContent = WI.UIString("Highest: %s").format(Number.isFinite(maxSize) ? Number.percentageString(maxSize / 100) : emDash);
+        this._detailsMinElement.textContent = WI.UIString("Lowest: %s").format(Number.isFinite(minSize) ? Number.percentageString(minSize / 100) : emDash);
+    }
+};
diff --git a/Source/WebInspectorUI/UserInterface/Views/ColumnChart.js b/Source/WebInspectorUI/UserInterface/Views/ColumnChart.js
new file mode 100644 (file)
index 0000000..13d3755
--- /dev/null
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2019 Apple Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+// ColumnChart creates a single filled line chart.
+//
+// Initialize the chart with a size. You can then include a new bar
+// in the bar chart by providing an (x, y, w, h) via `addBar`.
+//
+// SVG:
+//
+// - There is a single rect for each bar.
+//
+//  <div class="line-chart">
+//      <svg width="800" height="75" viewbox="0 0 800 75">
+//          <rect width="<w>" height="<h>" transform="translate(<x>, <y>)" />
+//          <rect width="<w>" height="<h>" transform="translate(<x>, <y>)" />
+//      </svg>
+//  </div>
+
+WI.ColumnChart = class ColumnChart
+{
+    constructor(size)
+    {
+        this._element = document.createElement("div");
+        this._element.classList.add("bar-chart");
+
+        this._svgElement = this._element.appendChild(createSVGElement("svg"));
+
+        this._bars = [];
+        this.size = size;
+    }
+
+    // Public
+
+    get element() { return this._element; }
+    get bars() { return this._bars; }
+
+    get size()
+    {
+        return this._size;
+    }
+
+    set size(size)
+    {
+        this._size = size;
+
+        this._svgElement.setAttribute("width", size.width);
+        this._svgElement.setAttribute("height", size.height);
+        this._svgElement.setAttribute("viewbox", `0 0 ${size.width} ${size.height}`);
+    }
+
+    addBar(x, y, width, height)
+    {
+        this._bars.push({x, y, width, height});
+    }
+
+    clear()
+    {
+        this._bars = [];
+    }
+
+    needsLayout()
+    {
+        if (this._scheduledLayoutUpdateIdentifier)
+            return;
+
+        this._scheduledLayoutUpdateIdentifier = requestAnimationFrame(this.updateLayout.bind(this));
+    }
+
+    updateLayout()
+    {
+        if (this._scheduledLayoutUpdateIdentifier) {
+            cancelAnimationFrame(this._scheduledLayoutUpdateIdentifier);
+            this._scheduledLayoutUpdateIdentifier = undefined;
+        }
+
+        this._svgElement.removeChildren();
+
+        for (let {x, y, width, height} of this._bars) {
+            let rect = this._svgElement.appendChild(createSVGElement("rect"));
+            rect.setAttribute("width", width);
+            rect.setAttribute("height", height);
+            rect.setAttribute("transform", `translate(${x}, ${y})`);
+        }
+    }
+};
index b151033..558e45e 100644 (file)
@@ -83,6 +83,9 @@ WI.ContentView = class ContentView extends WI.View
             if (timelineType === WI.TimelineRecord.Type.RenderingFrame)
                 return new WI.RenderingFrameTimelineView(representedObject, extraArguments);
 
+            if (timelineType === WI.TimelineRecord.Type.CPU)
+                return new WI.CPUTimelineView(representedObject, extraArguments);
+
             if (timelineType === WI.TimelineRecord.Type.Memory)
                 return new WI.MemoryTimelineView(representedObject, extraArguments);
 
index e9446a1..739f3f1 100644 (file)
 .memory-category-view > .details {
     width: 150px;
     padding-top: 10px;
-    font-family: '-webkit-system-font';
+    font-family: -webkit-system-font, sans-serif;
     font-size: 12px;
     color: hsl(0, 0%, 70%);
+    -webkit-padding-start: 15px;
 
-    --memory-category-view-details-padding-start: 15px;
     --memory-category-view-details-border-end: 1px solid var(--border-color);
 }
 
 body[dir=ltr] .memory-category-view > .details {
-    padding-left: var(--memory-category-view-details-padding-start);
     border-right: var(--memory-category-view-details-border-end);
 }
 
 body[dir=rtl] .memory-category-view > .details {
-    padding-right: var(--memory-category-view-details-padding-start);
     border-left: var(--memory-category-view-details-border-end);
 }
 
index 22d0137..89f6274 100644 (file)
  * THE POSSIBILITY OF SUCH DAMAGE.
  */
 
-WI.MemoryCategoryView = class MemoryCategoryView extends WI.Object
+WI.MemoryCategoryView = class MemoryCategoryView
 {
     constructor(category, displayName)
     {
-        super();
-
         console.assert(typeof category === "string");
 
         this._category = category;
@@ -51,7 +49,7 @@ WI.MemoryCategoryView = class MemoryCategoryView extends WI.Object
         this._graphElement = this._element.appendChild(document.createElement("div"));
         this._graphElement.classList.add("graph");
 
-        // FIXME: <https://webkit.org/b/153758> Web Inspector: Memory Timeline View should be responsive / resizable
+        // FIXME: <https://webkit.org/b/153758> Web Inspector: Memory / CPU Timeline Views should be responsive / resizable
         let size = new WI.Size(800, 75);
         this._chart = new WI.LineChart(size);
         this._graphElement.appendChild(this._chart.element);
@@ -66,6 +64,7 @@ WI.MemoryCategoryView = class MemoryCategoryView extends WI.Object
     {
         this._cachedMinSize = undefined;
         this._cachedMaxSize = undefined;
+        this._updateDetails(NaN, NaN);
 
         this._chart.clear();
         this._chart.needsLayout();
index 77f8b1d..1fa6401 100644 (file)
@@ -27,12 +27,12 @@ WI.MemoryTimelineOverviewGraph = class MemoryTimelineOverviewGraph extends WI.Ti
 {
     constructor(timeline, timelineOverview)
     {
+        console.assert(timeline instanceof WI.MemoryTimeline);
+
         super(timelineOverview);
 
         this.element.classList.add("memory");
 
-        console.assert(timeline instanceof WI.MemoryTimeline);
-
         this._memoryTimeline = timeline;
         this._memoryTimeline.addEventListener(WI.Timeline.Event.RecordAdded, this._memoryTimelineRecordAdded, this);
         this._memoryTimeline.addEventListener(WI.MemoryTimeline.Event.MemoryPressureEventAdded, this._memoryTimelineMemoryPressureEventAdded, this);
index 6e642b3..10e26b1 100644 (file)
@@ -39,7 +39,7 @@
 }
 
 .timeline-view.memory > .content .subtitle {
-    font-family: '-webkit-system-font';
+    font-family: -webkit-system-font, sans-serif;
     font-size: 14px;
 }
 
index f35a38b..577e905 100644 (file)
@@ -41,8 +41,7 @@ WI.MemoryTimelineView = class MemoryTimelineView extends WI.TimelineView
         let overviewElement = contentElement.appendChild(document.createElement("div"));
         overviewElement.classList.add("overview");
 
-        function createChartContainer(parentElement, subtitle, tooltip)
-        {
+        function createChartContainer(parentElement, subtitle, tooltip) {
             let chartElement = parentElement.appendChild(document.createElement("div"));
             chartElement.classList.add("chart");
 
@@ -120,11 +119,6 @@ WI.MemoryTimelineView = class MemoryTimelineView extends WI.TimelineView
         this._timelineRuler.updateLayout(WI.View.LayoutReason.Resize);
     }
 
-    hidden()
-    {
-        super.hidden();
-    }
-
     closed()
     {
         console.assert(this.representedObject instanceof WI.Timeline);
@@ -200,7 +194,7 @@ WI.MemoryTimelineView = class MemoryTimelineView extends WI.TimelineView
         this._maxComparisonCircleChart.updateLayout();
         this._updateMaxComparisonLegend(lastRecord.totalSize);
 
-        // FIXME: <https://webkit.org/b/153758> Web Inspector: Memory Timeline View should be responsive / resizable
+        // FIXME: <https://webkit.org/b/153758> Web Inspector: Memory / CPU Timeline Views should be responsive / resizable
         const categoryViewWidth = 800;
         const categoryViewHeight = 75;
 
@@ -310,8 +304,10 @@ WI.MemoryTimelineView = class MemoryTimelineView extends WI.TimelineView
 
     _clearMaxComparisonLegend()
     {
-        this._maxComparisonMaximumSizeElement.textContent = emDash;
-        this._maxComparisonCurrentSizeElement.textContent = emDash;
+        if (this._maxComparisonMaximumSizeElement)
+            this._maxComparisonMaximumSizeElement.textContent = emDash;
+        if (this._maxComparisonCurrentSizeElement)
+            this._maxComparisonCurrentSizeElement.textContent = emDash;
 
         let totalElement = this._maxComparisonCircleChart.centerElement.firstChild;
         if (totalElement)
index d7d3a40..ce05f3e 100644 (file)
@@ -24,7 +24,7 @@
  */
 
 .resource-sizes {
-    font-family: '-webkit-system-font';
+    font-family: -webkit-system-font, sans-serif;
 }
 
 .resource-sizes > .content {
index 9ef65e0..ca8e893 100644 (file)
     content: url(../Images/ScriptsInstrument.svg);
 }
 
+.cpu-icon .icon {
+    content: url(../Images/CPUInstrument.svg);
+}
+
 .memory-icon .icon {
     content: url(../Images/MemoryInstrument.svg);
 }
index a0e12d6..1e9f2f1 100644 (file)
@@ -63,6 +63,9 @@ WI.TimelineOverviewGraph = class TimelineOverviewGraph extends WI.View
         if (timelineType === WI.TimelineRecord.Type.RenderingFrame)
             return new WI.RenderingFrameTimelineOverviewGraph(timeline, timelineOverview);
 
+        if (timelineType === WI.TimelineRecord.Type.CPU)
+            return new WI.CPUTimelineOverviewGraph(timeline, timelineOverview);
+
         if (timelineType === WI.TimelineRecord.Type.Memory)
             return new WI.MemoryTimelineOverviewGraph(timeline, timelineOverview);
 
index c0adfb6..1e91a77 100644 (file)
@@ -116,6 +116,8 @@ WI.TimelineTabContentView = class TimelineTabContentView extends WI.ContentBrows
             return WI.UIString("JavaScript & Events");
         case WI.TimelineRecord.Type.RenderingFrame:
             return WI.UIString("Rendering Frames");
+        case WI.TimelineRecord.Type.CPU:
+            return WI.UIString("CPU");
         case WI.TimelineRecord.Type.Memory:
             return WI.UIString("Memory");
         case WI.TimelineRecord.Type.HeapAllocations:
@@ -136,6 +138,8 @@ WI.TimelineTabContentView = class TimelineTabContentView extends WI.ContentBrows
             return "network-icon";
         case WI.TimelineRecord.Type.Layout:
             return "layout-icon";
+        case WI.TimelineRecord.Type.CPU:
+            return "cpu-icon";
         case WI.TimelineRecord.Type.Memory:
             return "memory-icon";
         case WI.TimelineRecord.Type.HeapAllocations:
@@ -160,6 +164,8 @@ WI.TimelineTabContentView = class TimelineTabContentView extends WI.ContentBrows
             return "network";
         case WI.TimelineRecord.Type.Layout:
             return "colors";
+        case WI.TimelineRecord.Type.CPU:
+            return "cpu";
         case WI.TimelineRecord.Type.Memory:
             return "memory";
         case WI.TimelineRecord.Type.HeapAllocations:
@@ -248,6 +254,7 @@ WI.TimelineTabContentView = class TimelineTabContentView extends WI.ContentBrows
 
             break;
 
+        case WI.TimelineRecord.Type.CPU:
         case WI.TimelineRecord.Type.Memory:
             // Not used. Fall through to error just in case.
 
@@ -275,6 +282,7 @@ WI.TimelineTabContentView = class TimelineTabContentView extends WI.ContentBrows
             return WI.UIString("Snapshot %d").format(timelineRecord.heapSnapshot.identifier);
         case WI.TimelineRecord.Type.Media:
             return timelineRecord.displayName;
+        case WI.TimelineRecord.Type.CPU:
         case WI.TimelineRecord.Type.Memory:
             // Not used. Fall through to error just in case.
         default:
index ea103b6..ef4c554 100644 (file)
     --memory-max-comparison-fill-color: hsl(220, 10%, 75%);
     --memory-max-comparison-stroke-color: hsl(220, 10%, 55%);
 
+    --cpu-stroke-color: hsl(118, 33%, 42%);
+    --cpu-fill-color: hsl(118, 43%, 55%);
+
     --network-header-color: hsl(204, 52%, 55%);
     --network-system-color: hsl(79, 32%, 50%);
     --network-pseudo-header-color: hsl(312, 35%, 51%);