WebKit Bugzilla
Attachment 362328 Details for
Bug 194788
: Web Inspector: CPU Usage Timeline - Thread Breakdown
Home
|
New
|
Browse
|
Search
|
[?]
|
Reports
|
Requests
|
Help
|
New Account
|
Log In
Remember
[x]
|
Forgot Password
Login:
[x]
[patch]
[PATCH] Proposed Fix
bd-1.patch (text/plain), 80.10 KB, created by
Joseph Pecoraro
on 2019-02-18 14:01:08 PST
(
hide
)
Description:
[PATCH] Proposed Fix
Filename:
MIME Type:
Creator:
Joseph Pecoraro
Created:
2019-02-18 14:01:08 PST
Size:
80.10 KB
patch
obsolete
>diff --git a/Source/WebInspectorUI/ChangeLog b/Source/WebInspectorUI/ChangeLog >index 31ad4da29cd..71d3a1f07cf 100644 >--- a/Source/WebInspectorUI/ChangeLog >+++ b/Source/WebInspectorUI/ChangeLog >@@ -1,3 +1,148 @@ >+2019-02-18 Joseph Pecoraro <pecoraro@apple.com> >+ >+ Web Inspector: CPU Usage Timeline - Thread Breakdown >+ https://bugs.webkit.org/show_bug.cgi?id=194788 >+ >+ Reviewed by NOBODY (OOPS!). >+ >+ * Localizations/en.lproj/localizedStrings.js: >+ * UserInterface/Main.html: >+ New strings and files. >+ >+ * UserInterface/Views/Variables.css: >+ (:root): >+ New colors for cpu threads / activity breakdown. >+ >+ * UserInterface/Models/CPUTimelineRecord.js: >+ (WI.CPUTimelineRecord.prototype.get workers): >+ (WI.CPUTimelineRecord): >+ Distinguish the workres in a CPU timeline record. >+ >+ * UserInterface/Views/CPUTimelineOverviewGraph.js: >+ (WI.CPUTimelineOverviewGraph): >+ (WI.CPUTimelineOverviewGraph.prototype.layout): >+ * UserInterface/Views/CPUTimelineOverviewGraph.css: >+ (.timeline-overview-graph.cpu > .stacked-column-chart > svg > rect): >+ (.timeline-overview-graph.cpu > .stacked-column-chart > svg > rect.main-thread-usage): >+ (.timeline-overview-graph.cpu > .stacked-column-chart > svg > rect.worker-thread-usage): >+ (.timeline-overview-graph.cpu > .column-chart > svg > rect): >+ Stacked column chart for CPU in the overview graph. >+ >+ * UserInterface/Views/CPUTimelineView.css: >+ (.timeline-view.cpu > .content > .overview): >+ (.timeline-view.cpu > .content > .details > .subtitle.threads): >+ (.timeline-view.cpu > .content > .overview > .chart): >+ (.timeline-view.cpu > .content > .overview > .chart > .subtitle): >+ (.timeline-view.cpu > .content > .overview > .chart > .container): >+ (.timeline-view.cpu > .content > .overview > .divider): >+ (body[dir=ltr] .timeline-view.cpu > .content > .overview > .divider): >+ (body[dir=rtl] .timeline-view.cpu > .content > .overview > .divider): >+ (.timeline-view.cpu > .content > .overview .samples,): >+ (.timeline-view.cpu .legend): >+ (.timeline-view.cpu .legend .row): >+ (.timeline-view.cpu .legend .row + .row): >+ (.timeline-view.cpu .legend .swatch): >+ (.timeline-view.cpu .legend > .row > .swatch.sample-type-idle): >+ (.timeline-view.cpu .legend > .row > .swatch.sample-type-script): >+ (.timeline-view.cpu .legend > .row > .swatch.sample-type-style): >+ (.timeline-view.cpu .legend > .row > .swatch.sample-type-layout): >+ (.timeline-view.cpu .legend > .row > .swatch.sample-type-paint): >+ (.timeline-view.cpu .circle-chart > svg > path.segment.sample-type-idle): >+ (.timeline-view.cpu .circle-chart > svg > path.segment.sample-type-script): >+ (.timeline-view.cpu .circle-chart > svg > path.segment.sample-type-style): >+ (.timeline-view.cpu .circle-chart > svg > path.segment.sample-type-layout): >+ (.timeline-view.cpu .circle-chart > svg > path.segment.sample-type-paint): >+ (.timeline-view.cpu svg > path): >+ (.timeline-view.cpu .main-thread svg > path,): >+ (.timeline-view.cpu .worker-thread svg > path,): >+ (.timeline-view.cpu .cpu-usage-view.empty): >+ (.timeline-view.cpu :matches(.line-chart, .stacked-line-chart) .markers): >+ (.timeline-view.cpu :matches(.line-chart, .stacked-line-chart) .markers > div): >+ (.timeline-view.cpu :matches(.line-chart, .stacked-line-chart) .markers > div > .label): >+ (.timeline-view.cpu > .content): Deleted. >+ (.cpu-usage-view .line-chart > svg > path): Deleted. >+ (.timeline-view.cpu .legend > .row > .swatch.current): Deleted. >+ * UserInterface/Views/CPUTimelineView.js: >+ (WI.CPUTimelineView): >+ (WI.CPUTimelineView.displayNameForSampleType): >+ (WI.CPUTimelineView.prototype.shown): >+ (WI.CPUTimelineView.prototype.clear.clearUsageView): >+ (WI.CPUTimelineView.prototype.clear): >+ (WI.CPUTimelineView.prototype.initialLayout.createChartContainer): >+ (WI.CPUTimelineView.prototype.initialLayout.appendLegendRow): >+ (WI.CPUTimelineView.prototype.initialLayout): >+ (WI.CPUTimelineView.prototype.layout.removeGreaterThan): >+ (WI.CPUTimelineView.prototype.layout): >+ (WI.CPUTimelineView.prototype.layout.layoutView): >+ (WI.CPUTimelineView.prototype.layout.yScale): >+ (WI.CPUTimelineView.prototype._computeSamplingData.markRecordEntries): >+ (WI.CPUTimelineView.prototype._computeSamplingData): >+ (WI.CPUTimelineView.prototype._removeWorkerThreadViews): >+ (WI.CPUTimelineView.prototype._clearBreakdownLegend): >+ (WI.CPUTimelineView.prototype.layout.xScale): Deleted. >+ Line charts and Circle Chart for threads and breakdowns. >+ >+ * UserInterface/Views/CPUUsageStackedView.css: >+ (.cpu-usage-stacked-view): >+ (.cpu-usage-stacked-view > .details): >+ (body[dir=ltr] .cpu-usage-stacked-view > .details): >+ (body[dir=rtl] .cpu-usage-stacked-view > .details): >+ (.cpu-usage-stacked-view > .details > .name): >+ (body[dir=rtl] .cpu-usage-stacked-view > .graph): >+ (.cpu-usage-stacked-view > .graph): >+ (.cpu-usage-stacked-view > .graph,): >+ * UserInterface/Views/CPUUsageStackedView.js: >+ (WI.CPUUsageStackedView): >+ (WI.CPUUsageStackedView.prototype.get chart): >+ (WI.CPUUsageStackedView.prototype.clear): >+ (WI.CPUUsageStackedView.prototype.updateChart): >+ (WI.CPUUsageStackedView.prototype._updateDetails): >+ Same as CPUUsageView except Stacked for the total. >+ >+ * UserInterface/Views/CPUUsageView.css: >+ (.cpu-usage-view): >+ (.cpu-usage-view > .details): >+ (.cpu-usage-view > .details > .name): >+ (.cpu-usage-view > .graph): >+ * UserInterface/Views/CPUUsageView.js: >+ (WI.CPUUsageView): >+ (WI.CPUUsageView.prototype.get chart): >+ (WI.CPUUsageView.prototype.clear): >+ (WI.CPUUsageView.prototype.updateChart): >+ (WI.CPUUsageView.prototype._updateDetails): >+ Slight modifications for the new UI. >+ >+ * UserInterface/Views/LegacyCPUTimelineView.css: >+ (.timeline-view.legacy-cpu .cpu-usage-view .line-chart > svg > path): >+ * UserInterface/Views/LegacyCPUTimelineView.js: >+ (WI.LegacyCPUTimelineView.prototype.layout): >+ Update API calls in the legacy view for minor changes. >+ >+ * UserInterface/Views/MemoryCategoryView.css: >+ (.memory-category-view > .details): >+ (.memory-category-view > .details > .name): >+ * UserInterface/Views/MemoryTimelineOverviewGraph.js: >+ (WI.MemoryTimelineOverviewGraph.prototype.layout): >+ * UserInterface/Views/MemoryTimelineView.css: >+ (body .timeline-view.memory): >+ (.timeline-view.memory): Deleted. >+ Improvements ported from the CPU timeline views. >+ >+ * UserInterface/Views/StackedColumnChart.js: Added. >+ (WI.StackedColumnChart): >+ (WI.StackedColumnChart.prototype.get size): >+ (WI.StackedColumnChart.prototype.set size): >+ (WI.StackedColumnChart.prototype.initializeSections): >+ (WI.StackedColumnChart.prototype.addColumnSet): >+ (WI.StackedColumnChart.prototype.clear): >+ (WI.StackedColumnChart.prototype.layout): >+ A stacked column chart implementation. >+ >+ * UserInterface/Views/View.js: >+ (WI.View.prototype.removeUnparentedSubview): >+ Add a way to remove a subview that had its `element` moved >+ someplace other than a direct child of our element. >+ > 2019-02-18 Joseph Pecoraro <pecoraro@apple.com> > > Web Inspector: Better categorize CPU usage per-thread / worker >diff --git a/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js b/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js >index 020c043cf20..b29e9268319 100644 >--- a/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js >+++ b/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js >@@ -158,6 +158,7 @@ localizedStrings["Break on request with URL:"] = "Break on request with URL:"; > localizedStrings["Break on\u2026"] = "Break on\u2026"; > localizedStrings["Breakdown"] = "Breakdown"; > localizedStrings["Breakdown of each memory category at the end of the selected time range"] = "Breakdown of each memory category at the end of the selected time range"; >+localizedStrings["Breakdown of time spent on the main thread"] = "Breakdown of time spent on the main thread"; > localizedStrings["Breakpoint"] = "Breakpoint"; > localizedStrings["Breakpoints"] = "Breakpoints"; > localizedStrings["Breakpoints disabled"] = "Breakpoints disabled"; >@@ -593,6 +594,7 @@ localizedStrings["Lowest: %s"] = "Lowest: %s"; > localizedStrings["MIME Type"] = "MIME Type"; > localizedStrings["MIME Type:"] = "MIME Type:"; > localizedStrings["MSE Logging:"] = "MSE Logging:"; >+localizedStrings["Main Thread"] = "Main Thread"; > localizedStrings["Manifest URL"] = "Manifest URL"; > localizedStrings["Mass"] = "Mass"; > localizedStrings["Matching"] = "Matching"; >@@ -682,6 +684,7 @@ localizedStrings["Originally %s"] = "Originally %s"; > localizedStrings["Originator"] = "Originator"; > localizedStrings["Other"] = "Other"; > localizedStrings["Other Issue"] = "Other Issue"; >+localizedStrings["Other Threads"] = "Other Threads"; > localizedStrings["Other\u2026"] = "Other\u2026"; > localizedStrings["Outgoing message"] = "Outgoing message"; > localizedStrings["Output: "] = "Output: "; >@@ -940,6 +943,7 @@ localizedStrings["Stop recording canvas actions"] = "Stop recording canvas actio > localizedStrings["Stopping the \u201C%s\u201D audit"] = "Stopping the \u201C%s\u201D audit"; > localizedStrings["Storage"] = "Storage"; > localizedStrings["Style Attribute"] = "Style Attribute"; >+localizedStrings["Style Resolution"] = "Style Resolution"; > localizedStrings["Style rule"] = "Style rule"; > localizedStrings["Styles"] = "Styles"; > localizedStrings["Styles Invalidated"] = "Styles Invalidated"; >@@ -985,7 +989,9 @@ localizedStrings["This is what the result of an unsupported test with no data lo > localizedStrings["This object is a root"] = "This object is a root"; > localizedStrings["This object is referenced by internal objects"] = "This object is referenced by internal objects"; > localizedStrings["This text resource could benefit from compression"] = "This text resource could benefit from compression"; >+localizedStrings["Threads"] = "Threads"; > localizedStrings["Time"] = "Time"; >+localizedStrings["Time spent on the main thread"] = "Time spent on the main thread"; > localizedStrings["Time to First Byte"] = "Time to First Byte"; > localizedStrings["Timeline"] = "Timeline"; > localizedStrings["Timeline Recording %d"] = "Timeline Recording %d"; >@@ -1001,6 +1007,7 @@ localizedStrings["Timing"] = "Timing"; > localizedStrings["Toggle Classes"] = "Toggle Classes"; > localizedStrings["Toggle Visibility"] = "Toggle Visibility"; > localizedStrings["Top Functions"] = "Top Functions"; >+localizedStrings["Total"] = "Total"; > localizedStrings["Total Time"] = "Total Time"; > localizedStrings["Total memory size at the end of the selected time range"] = "Total memory size at the end of the selected time range"; > localizedStrings["Total time"] = "Total time"; >@@ -1059,6 +1066,7 @@ localizedStrings["Warnings"] = "Warnings"; > localizedStrings["Watch Expressions"] = "Watch Expressions"; > localizedStrings["Waterfall"] = "Waterfall"; > localizedStrings["Web Inspector"] = "Web Inspector"; >+localizedStrings["WebKit Threads"] = "WebKit Threads"; > localizedStrings["WebRTC"] = "WebRTC"; > localizedStrings["WebRTC Logging:"] = "WebRTC Logging:"; > localizedStrings["WebSocket Connection Established"] = "WebSocket Connection Established"; >@@ -1066,6 +1074,7 @@ localizedStrings["Whitespace characters"] = "Whitespace characters"; > localizedStrings["Width"] = "Width"; > localizedStrings["With Object Properties"] = "With Object Properties"; > localizedStrings["Worker"] = "Worker"; >+localizedStrings["Worker Thread"] = "Worker Thread"; > localizedStrings["Worker \u2014 %s"] = "Worker \u2014 %s"; > localizedStrings["Working Copy"] = "Working Copy"; > localizedStrings["Wrap lines to editor width"] = "Wrap lines to editor width"; >diff --git a/Source/WebInspectorUI/UserInterface/Main.html b/Source/WebInspectorUI/UserInterface/Main.html >index ceb24265e98..5f96b02273e 100644 >--- a/Source/WebInspectorUI/UserInterface/Main.html >+++ b/Source/WebInspectorUI/UserInterface/Main.html >@@ -45,6 +45,7 @@ > <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/CPUUsageStackedView.css"> > <link rel="stylesheet" href="Views/CPUUsageView.css"> > <link rel="stylesheet" href="Views/CallFrameIcons.css"> > <link rel="stylesheet" href="Views/CallFrameTreeElement.css"> >@@ -587,6 +588,7 @@ > <script src="Views/ButtonToolbarItem.js"></script> > <script src="Views/CPUTimelineOverviewGraph.js"></script> > <script src="Views/CPUTimelineView.js"></script> >+ <script src="Views/CPUUsageStackedView.js"></script> > <script src="Views/CPUUsageView.js"></script> > <script src="Views/CSSStyleSheetTreeElement.js"></script> > <script src="Views/CallFrameTreeElement.js"></script> >@@ -800,6 +802,7 @@ > <script src="Views/SpringEditor.js"></script> > <script src="Views/SVGImageResourceClusterContentView.js"></script> > <script src="Views/StackTraceView.js"></script> >+ <script src="Views/StackedColumnChart.js"></script> > <script src="Views/StackedLineChart.js"></script> > <script src="Views/StorageSidebarPanel.js"></script> > <script src="Views/SyntaxHighlightingSupport.js"></script> >diff --git a/Source/WebInspectorUI/UserInterface/Models/CPUTimelineRecord.js b/Source/WebInspectorUI/UserInterface/Models/CPUTimelineRecord.js >index 51ea2ae1b6a..90ddf05bb4b 100644 >--- a/Source/WebInspectorUI/UserInterface/Models/CPUTimelineRecord.js >+++ b/Source/WebInspectorUI/UserInterface/Models/CPUTimelineRecord.js >@@ -43,6 +43,7 @@ WI.CPUTimelineRecord = class CPUTimelineRecord extends WI.TimelineRecord > this._webkitThreadUsage = 0; > this._workerThreadUsage = 0; > this._unknownThreadUsage = 0; >+ this._workers = null; > > for (let thread of threads) { > if (thread.type === InspectorBackend.domains.CPUProfiler.ThreadInfoType.Main) { >@@ -52,10 +53,15 @@ WI.CPUTimelineRecord = class CPUTimelineRecord extends WI.TimelineRecord > } > > if (thread.type === InspectorBackend.domains.CPUProfiler.ThreadInfoType.WebKit) { >- if (thread.targetId) >+ if (thread.targetId) { >+ if (!this._workers) >+ this._workers = []; >+ this._workers.push({targetId: thread.targetId, usage: thread.usage}); > this._workerThreadUsage += thread.usage; >- else >- this._webkitThreadUsage += thread.usage; >+ continue; >+ } >+ >+ this._webkitThreadUsage += thread.usage; > continue; > } > >@@ -72,4 +78,5 @@ WI.CPUTimelineRecord = class CPUTimelineRecord extends WI.TimelineRecord > get webkitThreadUsage() { return this._webkitThreadUsage; } > get workerThreadUsage() { return this._workerThreadUsage; } > get unknownThreadUsage() { return this._unknownThreadUsage; } >+ get workers() { return this._workers; } > }; >diff --git a/Source/WebInspectorUI/UserInterface/Views/CPUTimelineOverviewGraph.css b/Source/WebInspectorUI/UserInterface/Views/CPUTimelineOverviewGraph.css >index c5d14c3f39f..1cedb376c8d 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/CPUTimelineOverviewGraph.css >+++ b/Source/WebInspectorUI/UserInterface/Views/CPUTimelineOverviewGraph.css >@@ -67,11 +67,29 @@ body[dir=rtl] .timeline-overview-graph.cpu > .legend { > } > } > >-body[dir=rtl] .timeline-overview-graph.cpu > .column-chart { >+body[dir=rtl] .timeline-overview-graph.cpu > .stacked-column-chart { > transform: scaleX(-1); > } > >-.timeline-overview-graph.cpu > .column-chart > svg > rect { >+.timeline-overview-graph.cpu > .stacked-column-chart > svg > rect { > stroke: var(--cpu-stroke-color); > fill: var(--cpu-fill-color); > } >+ >+.timeline-overview-graph.cpu > .stacked-column-chart > svg > rect.main-thread-usage { >+ fill: var(--cpu-main-thread-fill-color); >+} >+ >+.timeline-overview-graph.cpu > .stacked-column-chart > svg > rect.worker-thread-usage { >+ fill: var(--cpu-worker-thread-fill-color); >+} >+ >+/* LegacyCPUTimeline */ >+.timeline-overview-graph.cpu > .column-chart > svg > rect { >+ stroke: var(--cpu-stroke-color); >+ fill: var(--cpu-main-thread-fill-color); >+} >+ >+body[dir=rtl] .timeline-overview-graph.cpu > .column-chart { >+ transform: scaleX(-1); >+} >diff --git a/Source/WebInspectorUI/UserInterface/Views/CPUTimelineOverviewGraph.js b/Source/WebInspectorUI/UserInterface/Views/CPUTimelineOverviewGraph.js >index 221d88f657c..ba7b798fe66 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/CPUTimelineOverviewGraph.js >+++ b/Source/WebInspectorUI/UserInterface/Views/CPUTimelineOverviewGraph.js >@@ -38,7 +38,11 @@ WI.CPUTimelineOverviewGraph = class CPUTimelineOverviewGraph extends WI.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); >+ if (WI.settings.experimentalEnableCPUUsageEnhancements.value) { >+ this._chart = new WI.StackedColumnChart(size); >+ this._chart.initializeSections(["main-thread-usage", "worker-thread-usage", "total-usage"]); >+ } else >+ this._chart = new WI.ColumnChart(size); > this.addSubview(this._chart); > this.element.appendChild(this._chart.element); > >@@ -114,13 +118,15 @@ WI.CPUTimelineOverviewGraph = class CPUTimelineOverviewGraph extends WI.Timeline > // Bars for each record. > for (let record of visibleRecords) { > let w = intervalWidth; >- let h = Math.max(minimumDisplayHeight, yScale(record.usage)); >+ let h1 = Math.max(minimumDisplayHeight, yScale(record.mainThreadUsage)); >+ let h2 = Math.max(minimumDisplayHeight, yScale(record.mainThreadUsage + record.workerThreadUsage)); >+ let h3 = Math.max(minimumDisplayHeight, yScale(record.usage)); > let x = xScale(record.startTime - (samplingRatePerSecond / 2)); >- let y = height - h; >- this._chart.addColumn(x, y, w, h); >+ if (WI.settings.experimentalEnableCPUUsageEnhancements.value) >+ this._chart.addColumnSet(x, height, w, [h1, h2, h3]); >+ else >+ this._chart.addColumn(x, height - h3, w, h3); > } >- >- this._chart.updateLayout(); > } > > // Private >diff --git a/Source/WebInspectorUI/UserInterface/Views/CPUTimelineView.css b/Source/WebInspectorUI/UserInterface/Views/CPUTimelineView.css >index ef4c78e2631..584b746f74f 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/CPUTimelineView.css >+++ b/Source/WebInspectorUI/UserInterface/Views/CPUTimelineView.css >@@ -27,8 +27,12 @@ body .timeline-view.cpu { > overflow: scroll; > } > >-.timeline-view.cpu > .content { >- margin-top: 10px; >+.timeline-view.cpu > .content > .overview { >+ display: flex; >+ justify-content: center; >+ padding: 10px; >+ margin-bottom: 10px; >+ border-bottom: 1px solid var(--border-color); > } > > .timeline-view.cpu > .content .subtitle { >@@ -63,12 +67,157 @@ body[dir=rtl] .timeline-view.cpu > .content > .details > .timeline-ruler { > border-bottom: 1px solid var(--border-color); > } > >-.cpu-usage-view .line-chart > svg > path { >+.timeline-view.cpu > .content > .details > .subtitle.threads { >+ position: relative; >+ padding-top: 10px; >+ background-color: var(--background-color-content); >+ z-index: calc(var(--timeline-marker-z-index) + 1); >+} >+ >+.timeline-view.cpu > .content > .overview > .chart { >+ width: 420px; >+ text-align: center; >+} >+ >+.timeline-view.cpu > .content > .overview > .chart > .subtitle { >+ margin-bottom: 1em; >+} >+ >+.timeline-view.cpu > .content > .overview > .chart > .container { >+ display: flex; >+ justify-content: center; >+} >+ >+.timeline-view.cpu > .content > .overview > .divider { >+ margin: 0 5px; >+ >+ --cpu-timeline-view-overview-divider-border-end: 1px solid var(--border-color); >+} >+ >+body[dir=ltr] .timeline-view.cpu > .content > .overview > .divider { >+ border-right: var(--cpu-timeline-view-overview-divider-border-end); >+} >+ >+body[dir=rtl] .timeline-view.cpu > .content > .overview > .divider { >+ border-left: var(--cpu-timeline-view-overview-divider-border-end); >+} >+ >+.timeline-view.cpu > .content > .overview .samples, >+.timeline-view.cpu > .content > .overview .legend .size { >+ margin: auto; >+ color: var(--text-color-secondary); >+} >+ >+.timeline-view.cpu .legend { >+ -webkit-padding-start: 20px; >+ text-align: start; >+} >+ >+.timeline-view.cpu .legend .row { >+ display: flex; >+} >+ >+.timeline-view.cpu .legend .row + .row { >+ margin-top: 4px; >+} >+ >+.timeline-view.cpu .legend .swatch { >+ width: 1em; >+ height: 1em; >+ margin-top: 1px; >+ -webkit-margin-end: 8px; >+} >+ >+.timeline-view.cpu .legend > .row > .swatch.sample-type-idle { >+ border: 1px solid var(--cpu-idle-stroke-color); >+ background: var(--cpu-idle-fill-color); >+} >+ >+.timeline-view.cpu .legend > .row > .swatch.sample-type-script { >+ border: 1px solid var(--cpu-script-stroke-color); >+ background: var(--cpu-script-fill-color); >+} >+ >+.timeline-view.cpu .legend > .row > .swatch.sample-type-style { >+ border: 1px solid var(--cpu-style-stroke-color); >+ background: var(--cpu-style-fill-color); >+} >+ >+.timeline-view.cpu .legend > .row > .swatch.sample-type-layout { >+ border: 1px solid var(--cpu-layout-stroke-color); >+ background: var(--cpu-layout-fill-color); >+} >+ >+.timeline-view.cpu .legend > .row > .swatch.sample-type-paint { >+ border: 1px solid var(--cpu-paint-stroke-color); >+ background: var(--cpu-paint-fill-color); >+} >+ >+.timeline-view.cpu .circle-chart > svg > path.segment.sample-type-idle { >+ stroke: var(--cpu-idle-stroke-color); >+ fill: var(--cpu-idle-fill-color); >+} >+ >+.timeline-view.cpu .circle-chart > svg > path.segment.sample-type-script { >+ stroke: var(--cpu-script-stroke-color); >+ fill: var(--cpu-script-fill-color); >+} >+ >+.timeline-view.cpu .circle-chart > svg > path.segment.sample-type-style { >+ stroke: var(--cpu-style-stroke-color); >+ fill: var(--cpu-style-fill-color); >+} >+ >+.timeline-view.cpu .circle-chart > svg > path.segment.sample-type-layout { >+ stroke: var(--cpu-layout-stroke-color); >+ fill: var(--cpu-layout-fill-color); >+} >+ >+.timeline-view.cpu .circle-chart > svg > path.segment.sample-type-paint { >+ stroke: var(--cpu-paint-stroke-color); >+ fill: var(--cpu-paint-fill-color); >+} >+ >+.timeline-view.cpu 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); >+.timeline-view.cpu .main-thread svg > path, >+.timeline-view.cpu svg > path.main-thread-usage { >+ fill: var(--cpu-main-thread-fill-color); >+} >+ >+.timeline-view.cpu .worker-thread svg > path, >+.timeline-view.cpu svg > path.worker-thread-usage { >+ fill: var(--cpu-worker-thread-fill-color); >+} >+ >+.timeline-view.cpu .cpu-usage-view.empty { >+ display: none; >+} >+ >+.timeline-view.cpu :matches(.line-chart, .stacked-line-chart) .markers { >+ position: absolute; >+ top: 0; >+ left: 0; >+ right: 0; >+ bottom: 0; >+} >+ >+.timeline-view.cpu :matches(.line-chart, .stacked-line-chart) .markers > div { >+ position: absolute; >+ z-index: 10; >+ width: 100%; >+ height: 1px; >+ background-color: hsla(0, 0%, var(--foreground-lightness), 0.07); >+ text-align: right; >+ pointer-events: none; >+} >+ >+.timeline-view.cpu :matches(.line-chart, .stacked-line-chart) .markers > div > .label { >+ padding: 2px; >+ font-size: 8px; >+ color: var(--text-color-secondary); >+ background-color: var(--background-color-content); > } >diff --git a/Source/WebInspectorUI/UserInterface/Views/CPUTimelineView.js b/Source/WebInspectorUI/UserInterface/Views/CPUTimelineView.js >index 34e31833afa..44725a13e0e 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/CPUTimelineView.js >+++ b/Source/WebInspectorUI/UserInterface/Views/CPUTimelineView.js >@@ -35,36 +35,33 @@ WI.CPUTimelineView = class CPUTimelineView extends WI.TimelineView > > 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.addSubview(this._cpuUsageView); >- this._detailsContainerElement.appendChild(this._cpuUsageView.element); >- > timeline.addEventListener(WI.Timeline.Event.RecordAdded, this._cpuTimelineRecordAdded, this); > } > >+ // Static >+ >+ static displayNameForSampleType(type) >+ { >+ switch (type) { >+ case WI.CPUTimelineView.SampleType.Script: >+ return WI.UIString("Script"); >+ case WI.CPUTimelineView.SampleType.Layout: >+ return WI.UIString("Layout"); >+ case WI.CPUTimelineView.SampleType.Paint: >+ return WI.UIString("Paint"); >+ case WI.CPUTimelineView.SampleType.Style: >+ return WI.UIString("Style Resolution"); >+ } >+ } >+ > // Public > > shown() > { > super.shown(); > >- this._timelineRuler.updateLayout(WI.View.LayoutReason.Resize); >+ if (this._timelineRuler) >+ this._timelineRuler.updateLayout(WI.View.LayoutReason.Resize); > } > > closed() >@@ -82,7 +79,27 @@ WI.CPUTimelineView = class CPUTimelineView extends WI.TimelineView > > clear() > { >- this._cpuUsageView.clear(); >+ if (!this.didInitialLayout) >+ return; >+ >+ this._breakdownChart.clear(); >+ this._breakdownChart.needsLayout(); >+ this._clearBreakdownLegend(); >+ >+ function clearUsageView(view) { >+ view.clear(); >+ >+ let markersElement = view.chart.element.querySelector(".markers"); >+ if (markersElement) >+ markersElement.remove(); >+ } >+ >+ clearUsageView(this._cpuUsageView); >+ clearUsageView(this._mainThreadUsageView); >+ clearUsageView(this._webkitThreadUsageView); >+ clearUsageView(this._unknownThreadUsageView); >+ >+ this._removeWorkerThreadViews(); > } > > get scrollableElements() >@@ -94,6 +111,100 @@ WI.CPUTimelineView = class CPUTimelineView extends WI.TimelineView > > get showsFilterBar() { return false; } > >+ initialLayout() >+ { >+ let contentElement = this.element.appendChild(document.createElement("div")); >+ contentElement.classList.add("content"); >+ >+ let overviewElement = contentElement.appendChild(document.createElement("div")); >+ overviewElement.classList.add("overview"); >+ >+ function createChartContainer(parentElement, subtitle, tooltip) { >+ let chartElement = parentElement.appendChild(document.createElement("div")); >+ chartElement.classList.add("chart"); >+ >+ let chartSubtitleElement = chartElement.appendChild(document.createElement("div")); >+ chartSubtitleElement.classList.add("subtitle"); >+ chartSubtitleElement.textContent = subtitle; >+ chartSubtitleElement.title = tooltip; >+ >+ let chartFlexContainerElement = chartElement.appendChild(document.createElement("div")); >+ chartFlexContainerElement.classList.add("container"); >+ return chartFlexContainerElement; >+ } >+ >+ function appendLegendRow(legendElement, swatchClass, label) { >+ let rowElement = legendElement.appendChild(document.createElement("div")); >+ rowElement.classList.add("row"); >+ >+ let swatchElement = rowElement.appendChild(document.createElement("div")); >+ swatchElement.classList.add("swatch", swatchClass); >+ >+ let valueContainer = rowElement.appendChild(document.createElement("div")); >+ valueContainer.classList.add("value"); >+ >+ let labelElement = valueContainer.appendChild(document.createElement("div")); >+ labelElement.classList.add("label"); >+ labelElement.textContent = label; >+ >+ let sizeElement = valueContainer.appendChild(document.createElement("div")); >+ sizeElement.classList.add("size"); >+ >+ return sizeElement; >+ } >+ >+ let breakdownTooltip = WI.UIString("Breakdown of time spent on the main thread"); >+ let breakdownChartContainerElement = createChartContainer(overviewElement, WI.UIString("Main Thread"), breakdownTooltip); >+ this._breakdownChart = new WI.CircleChart({size: 120, innerRadiusRatio: 0.5}); >+ this._breakdownChart.segments = Object.values(WI.CPUTimelineView.SampleType); >+ this.addSubview(this._breakdownChart); >+ breakdownChartContainerElement.appendChild(this._breakdownChart.element); >+ >+ this._breakdownLegendElement = breakdownChartContainerElement.appendChild(document.createElement("div")); >+ this._breakdownLegendElement.classList.add("legend"); >+ >+ this._breakdownLegendScriptElement = appendLegendRow.call(this, this._breakdownLegendElement, WI.CPUTimelineView.SampleType.Script, WI.CPUTimelineView.displayNameForSampleType(WI.CPUTimelineView.SampleType.Script)); >+ this._breakdownLegendLayoutElement = appendLegendRow.call(this, this._breakdownLegendElement, WI.CPUTimelineView.SampleType.Layout, WI.CPUTimelineView.displayNameForSampleType(WI.CPUTimelineView.SampleType.Layout)); >+ this._breakdownLegendPaintElement = appendLegendRow.call(this, this._breakdownLegendElement, WI.CPUTimelineView.SampleType.Paint, WI.CPUTimelineView.displayNameForSampleType(WI.CPUTimelineView.SampleType.Paint)); >+ this._breakdownLegendStyleElement = appendLegendRow.call(this, this._breakdownLegendElement, WI.CPUTimelineView.SampleType.Style, WI.CPUTimelineView.displayNameForSampleType(WI.CPUTimelineView.SampleType.Style)); >+ >+ 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.CPUUsageStackedView(WI.UIString("Total")); >+ this.addSubview(this._cpuUsageView); >+ this._detailsContainerElement.appendChild(this._cpuUsageView.element); >+ >+ let threadsSubtitleElement = detailsContainerElement.appendChild(document.createElement("div")); >+ threadsSubtitleElement.classList.add("subtitle", "threads"); >+ threadsSubtitleElement.textContent = WI.UIString("Threads"); >+ >+ this._mainThreadUsageView = new WI.CPUUsageView(WI.UIString("Main Thread")); >+ this._mainThreadUsageView.element.classList.add("main-thread"); >+ this.addSubview(this._mainThreadUsageView); >+ this._detailsContainerElement.appendChild(this._mainThreadUsageView.element); >+ >+ this._webkitThreadUsageView = new WI.CPUUsageView(WI.UIString("WebKit Threads")); >+ this._webkitThreadUsageView.element.classList.add("non-main-thread"); >+ this.addSubview(this._webkitThreadUsageView); >+ this._detailsContainerElement.appendChild(this._webkitThreadUsageView.element); >+ >+ this._unknownThreadUsageView = new WI.CPUUsageView(WI.UIString("Other Threads")); >+ this._unknownThreadUsageView.element.classList.add("non-main-thread"); >+ this.addSubview(this._unknownThreadUsageView); >+ this._detailsContainerElement.appendChild(this._unknownThreadUsageView.element); >+ >+ this._workerViews = []; >+ } >+ > layout() > { > if (this.layoutReason === WI.View.LayoutReason.Resize) >@@ -104,7 +215,8 @@ WI.CPUTimelineView = class CPUTimelineView extends WI.TimelineView > this._timelineRuler.startTime = this.startTime; > this._timelineRuler.endTime = this.endTime; > >- const cpuUsageViewHeight = 75; // Keep this in sync with .cpu-usage-view >+ const cpuUsageViewHeight = 150; // Keep this in sync with .cpu-usage-stacked-view >+ const threadCPUUsageViewHeight = 65; // Keep this in sync with .cpu-usage-view > > let graphStartTime = this.startTime; > let graphEndTime = this.endTime; >@@ -112,6 +224,7 @@ WI.CPUTimelineView = class CPUTimelineView extends WI.TimelineView > let visibleEndTime = Math.min(this.endTime, this.currentTime); > > let discontinuities = this._recording.discontinuitiesInTimeRange(graphStartTime, visibleEndTime); >+ let originalDiscontinuities = discontinuities.slice(0); > > // 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; >@@ -121,72 +234,404 @@ WI.CPUTimelineView = class CPUTimelineView extends WI.TimelineView > return; > } > >- // Update total usage chart with the last record's data. >- let lastRecord = visibleRecords.lastValue; >+ let samplingData = this._computeSamplingData(graphStartTime, visibleEndTime); >+ let nonIdleSamplesCount = samplingData.samples.length - samplingData.samplesIdle; >+ if (!nonIdleSamplesCount) { >+ this._breakdownChart.clear(); >+ this._breakdownChart.needsLayout(); >+ this._clearBreakdownLegend(); >+ } else { >+ let percentScript = samplingData.samplesScript / nonIdleSamplesCount; >+ let percentLayout = samplingData.samplesLayout / nonIdleSamplesCount; >+ let percentPaint = samplingData.samplesPaint / nonIdleSamplesCount; >+ let percentStyle = samplingData.samplesStyle / nonIdleSamplesCount; > >- // FIXME: Left chart. >- // FIXME: Right chart. >+ this._breakdownLegendScriptElement.textContent = `${Number.percentageString(percentScript)} (${samplingData.samplesScript})`; >+ this._breakdownLegendLayoutElement.textContent = `${Number.percentageString(percentLayout)} (${samplingData.samplesLayout})`; >+ this._breakdownLegendPaintElement.textContent = `${Number.percentageString(percentPaint)} (${samplingData.samplesPaint})`; >+ this._breakdownLegendStyleElement.textContent = `${Number.percentageString(percentStyle)} (${samplingData.samplesStyle})`; >+ >+ this._breakdownChart.values = [percentScript * 100, percentLayout * 100, percentPaint * 100, percentStyle * 100]; >+ this._breakdownChart.needsLayout(); >+ >+ let centerElement = this._breakdownChart.centerElement; >+ let samplesElement = centerElement.firstChild; >+ if (!samplesElement) { >+ samplesElement = centerElement.appendChild(document.createElement("div")); >+ samplesElement.classList.add("samples"); >+ samplesElement.title = WI.UIString("Time spent on the main thread"); >+ } >+ >+ let millisecondsStringNoDecimal = WI.UIString("%.0fms").format(nonIdleSamplesCount); >+ samplesElement.textContent = millisecondsStringNoDecimal; >+ } > > let dataPoints = []; >+ let workers = new Map; >+ > let max = -Infinity; >+ let mainThreadMax = -Infinity; >+ let webkitThreadMax = -Infinity; >+ let unknownThreadMax = -Infinity; >+ > let min = Infinity; >+ let mainThreadMin = Infinity; >+ let webkitThreadMin = Infinity; >+ let unknownThreadMin = Infinity; >+ > let average = 0; >+ let mainThreadAverage = 0; >+ let webkitThreadAverage = 0; >+ let unknownThreadAverage = 0; > > for (let record of visibleRecords) { > let time = record.startTime; >- let usage = record.usage; >+ let {usage, mainThreadUsage, workerThreadUsage, webkitThreadUsage, unknownThreadUsage} = record; > > if (discontinuities.length && discontinuities[0].endTime < time) { > let startDiscontinuity = discontinuities.shift(); > let endDiscontinuity = startDiscontinuity; > while (discontinuities.length && discontinuities[0].endTime < time) > endDiscontinuity = discontinuities.shift(); >- dataPoints.push({time: startDiscontinuity.startTime, size: 0}); >- dataPoints.push({time: endDiscontinuity.endTime, size: 0}); >- dataPoints.push({time: endDiscontinuity.endTime, size: usage}); >+ >+ if (dataPoints.length) { >+ let previousDataPoint = dataPoints.lastValue; >+ dataPoints.push({time: startDiscontinuity.startTime, mainThreadUsage: previousDataPoint.mainThreadUsage, workerThreadUsage: previousDataPoint.workerThreadUsage, webkitThreadUsage: previousDataPoint.webkitThreadUsage, unknownThreadUsage: previousDataPoint.unknownThreadUsage, usage: previousDataPoint.usage}); >+ } >+ >+ dataPoints.push({time: startDiscontinuity.startTime, mainThreadUsage: 0, workerThreadUsage: 0, webkitThreadUsage: 0, unknownThreadUsage: 0, usage: 0}); >+ dataPoints.push({time: endDiscontinuity.endTime, mainThreadUsage: 0, workerThreadUsage: 0, webkitThreadUsage: 0, unknownThreadUsage: 0, usage: 0}); >+ dataPoints.push({time: endDiscontinuity.endTime, mainThreadUsage, workerThreadUsage, webkitThreadUsage, unknownThreadUsage, usage}); > } > >- dataPoints.push({time, size: usage}); >+ dataPoints.push({time, mainThreadUsage, workerThreadUsage, webkitThreadUsage, unknownThreadUsage, usage}); >+ > max = Math.max(max, usage); >+ mainThreadMax = Math.max(mainThreadMax, mainThreadUsage); >+ webkitThreadMax = Math.max(webkitThreadMax, webkitThreadUsage); >+ unknownThreadMax = Math.max(unknownThreadMax, unknownThreadUsage); >+ > min = Math.min(min, usage); >+ mainThreadMin = Math.min(mainThreadMin, mainThreadUsage); >+ webkitThreadMin = Math.min(webkitThreadMin, webkitThreadUsage); >+ unknownThreadMin = Math.min(unknownThreadMin, unknownThreadUsage); >+ > average += usage; >+ mainThreadAverage += mainThreadUsage; >+ webkitThreadAverage += webkitThreadUsage; >+ unknownThreadAverage += unknownThreadUsage; >+ >+ if (record.workers && record.workers.length) { >+ for (let {targetId, usage} of record.workers) { >+ let workerData = workers.get(targetId); >+ if (!workerData) { >+ workerData = {discontinuities: originalDiscontinuities.slice(0), recordsCount: 0, dataPoints: [], min: Infinity, max: -Infinity, average: 0}; >+ while (workerData.discontinuities.length && workerData.discontinuities[0].endTime <= graphStartTime) >+ workerData.discontinuities.shift(); >+ workerData.dataPoints.push({time: graphStartTime, usage: 0}); >+ workerData.dataPoints.push({time, usage: 0}); >+ workers.set(targetId, workerData); >+ } >+ >+ if (workerData.discontinuities.length && workerData.discontinuities[0].endTime < time) { >+ let startDiscontinuity = workerData.discontinuities.shift(); >+ let endDiscontinuity = startDiscontinuity; >+ while (workerData.discontinuities.length && workerData.discontinuities[0].endTime < time) >+ endDiscontinuity = workerData.discontinuities.shift(); >+ if (workerData.dataPoints.length) { >+ let previousDataPoint = workerData.dataPoints.lastValue; >+ workerData.dataPoints.push({time: startDiscontinuity.startTime, usage: previousDataPoint.usage}); >+ } >+ workerData.dataPoints.push({time: startDiscontinuity.startTime, usage: 0}); >+ workerData.dataPoints.push({time: endDiscontinuity.endTime, usage: 0}); >+ workerData.dataPoints.push({time: endDiscontinuity.endTime, usage}); >+ } >+ >+ workerData.dataPoints.push({time, usage}); >+ workerData.recordsCount += 1; >+ workerData.max = Math.max(workerData.max, usage); >+ workerData.min = Math.min(workerData.min, usage); >+ workerData.average += usage; >+ } >+ } > } > > average /= visibleRecords.length; >+ mainThreadAverage /= visibleRecords.length; >+ webkitThreadAverage /= visibleRecords.length; >+ unknownThreadAverage /= visibleRecords.length; >+ >+ for (let [workerId, workerData] of workers) >+ workerData.average = workerData.average / workerData.recordsCount; > > // 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}) { >+ function removeGreaterThan(arr, max) { >+ return arr.filter((x) => x <= max); >+ } >+ >+ function markerValuesForMaxValue(max) { >+ if (max < 1) >+ return [0.5]; >+ if (max < 7) >+ return removeGreaterThan([1, 3, 5], max); >+ if (max < 12.5) >+ return removeGreaterThan([5, 10], max); >+ if (max < 20) >+ return removeGreaterThan([5, 10, 15], max); >+ if (max < 30) >+ return removeGreaterThan([10, 20, 30], max); >+ if (max < 50) >+ return removeGreaterThan([15, 30, 45], max); >+ if (max < 100) >+ return removeGreaterThan([25, 50, 75], max); >+ if (max < 200) >+ return removeGreaterThan([50, 100, 150], max); >+ if (max >= 200) { >+ let hundreds = Math.floor(max / 100); >+ let even = (hundreds % 2) === 0; >+ if (even) { >+ let top = hundreds * 100; >+ let bottom = top / 2; >+ return [bottom, top]; >+ } >+ let top = hundreds * 100; >+ let bottom = 100; >+ let mid = (top + bottom) / 2; >+ return [bottom, mid, top]; >+ } >+ } >+ >+ function layoutView(view, property, graphHeight, {dataPoints, layoutMax, min, max, average}) { > if (min === Infinity) > min = 0; > if (max === -Infinity) > max = 0; >+ if (layoutMax === -Infinity) >+ layoutMax = 0; > >- // Zoom in to the top of each graph to accentuate small changes. >- let graphMin = min * 0.95; >- let graphMax = (max * 1.05) - graphMin; >+ let isAllThreadsGraph = property === null; >+ >+ let graphMin = 0; >+ let graphMax = (layoutMax * 1.05); > > function xScale(time) { > return (time - graphStartTime) / secondsPerPixel; > } > >- let size = new WI.Size(xScale(graphEndTime), cpuUsageViewHeight); >- > function yScale(value) { >- return size.height - (((value - graphMin) / graphMax) * size.height); >+ return graphHeight - (((value - graphMin) / graphMax) * graphHeight); > } > >- view.updateChart(dataPoints, size, visibleEndTime, min, max, average, xScale, yScale); >+ let size = new WI.Size(xScale(graphEndTime), graphHeight); >+ >+ view.updateChart(dataPoints, size, visibleEndTime, min, max, average, xScale, yScale, property); >+ >+ let markersElement = view.chart.element.querySelector(".markers"); >+ if (!markersElement) { >+ markersElement = view.chart.element.appendChild(document.createElement("div")); >+ markersElement.className = "markers"; >+ } >+ markersElement.removeChildren(); >+ >+ let markerValues; >+ if (isAllThreadsGraph) >+ markerValues = markerValuesForMaxValue(max); >+ else { >+ const minimumMarkerTextHeight = 17; >+ let percentPerPixel = 1 / (graphHeight / layoutMax); >+ if (layoutMax < 5) { >+ let minimumDisplayablePercentByTwo = Math.ceil((minimumMarkerTextHeight * percentPerPixel) / 2) * 2; >+ markerValues = [Math.max(minimumDisplayablePercentByTwo, Math.floor(max))]; >+ } else { >+ let minimumDisplayablePercentByFive = Math.ceil((minimumMarkerTextHeight * percentPerPixel) / 5) * 5; >+ markerValues = [Math.max(minimumDisplayablePercentByFive, Math.floor(max))]; >+ } >+ } >+ >+ for (let value of markerValues) { >+ let marginTop = yScale(value); >+ >+ let markerElement = markersElement.appendChild(document.createElement("div")); >+ markerElement.style.marginTop = marginTop.toFixed(2) + "px"; >+ >+ let labelElement = markerElement.appendChild(document.createElement("span")); >+ labelElement.classList.add("label"); >+ labelElement.innerText = Number.percentageString(value / 100, 0); >+ } > } > >- layoutView(this._cpuUsageView, {dataPoints, min, max, average}); >+ // Layout all graphs to the same time scale. The maximum value is >+ // the maximum total CPU usage across all threads. >+ let layoutMax = max; >+ >+ layoutView(this._cpuUsageView, null, cpuUsageViewHeight, {dataPoints, layoutMax, min, max, average}); >+ layoutView(this._mainThreadUsageView, "mainThreadUsage", threadCPUUsageViewHeight, {dataPoints, layoutMax, min: mainThreadMin, max: mainThreadMax, average: mainThreadAverage}); >+ layoutView(this._webkitThreadUsageView, "webkitThreadUsage", threadCPUUsageViewHeight, {dataPoints, layoutMax, min: webkitThreadMin, max: webkitThreadMax, average: webkitThreadAverage}); >+ layoutView(this._unknownThreadUsageView, "unknownThreadUsage", threadCPUUsageViewHeight, {dataPoints, layoutMax, min: unknownThreadMin, max: unknownThreadMax, average: unknownThreadAverage}); >+ >+ this._removeWorkerThreadViews(); >+ >+ for (let [workerId, workerData] of workers) { >+ let worker = WI.targetManager.targetForIdentifier(workerId); >+ let displayName = worker ? worker.displayName : WI.UIString("Worker Thread"); >+ let workerView = new WI.CPUUsageView(displayName); >+ workerView.element.classList.add("worker-thread"); >+ this.addSubview(workerView); >+ this._detailsContainerElement.insertBefore(workerView.element, this._webkitThreadUsageView.element); >+ this._workerViews.push(workerView); >+ >+ layoutView(workerView, "usage", threadCPUUsageViewHeight, {dataPoints: workerData.dataPoints, layoutMax, min: workerData.min, max: workerData.max, average: workerData.average}); >+ } > } > > // Private > >+ _computeSamplingData(startTime, endTime) >+ { >+ const includeRecordBeforeStart = true; >+ >+ let scriptTimeline = this._recording.timelineForRecordType(WI.TimelineRecord.Type.Script); >+ let scriptRecords = scriptTimeline ? scriptTimeline.recordsInTimeRange(startTime, endTime, includeRecordBeforeStart) : []; >+ scriptRecords = scriptRecords.filter((record) => { >+ switch (record.eventType) { >+ case WI.ScriptTimelineRecord.EventType.ScriptEvaluated: >+ case WI.ScriptTimelineRecord.EventType.APIScriptEvaluated: >+ case WI.ScriptTimelineRecord.EventType.ObserverCallback: >+ case WI.ScriptTimelineRecord.EventType.EventDispatched: >+ case WI.ScriptTimelineRecord.EventType.MicrotaskDispatched: >+ case WI.ScriptTimelineRecord.EventType.TimerFired: >+ case WI.ScriptTimelineRecord.EventType.AnimationFrameFired: >+ return true; >+ >+ case WI.ScriptTimelineRecord.EventType.AnimationFrameRequested: >+ case WI.ScriptTimelineRecord.EventType.AnimationFrameCanceled: >+ case WI.ScriptTimelineRecord.EventType.TimerInstalled: >+ case WI.ScriptTimelineRecord.EventType.TimerRemoved: >+ case WI.ScriptTimelineRecord.EventType.ProbeSampleRecorded: >+ case WI.ScriptTimelineRecord.EventType.ConsoleProfileRecorded: >+ case WI.ScriptTimelineRecord.EventType.GarbageCollected: >+ return false; >+ } >+ }); >+ >+ let layoutTimeline = this._recording.timelineForRecordType(WI.TimelineRecord.Type.Layout); >+ let layoutRecords = layoutTimeline ? layoutTimeline.recordsInTimeRange(startTime, endTime, includeRecordBeforeStart) : []; >+ layoutRecords = layoutRecords.filter((record) => { >+ switch (record.eventType) { >+ case WI.LayoutTimelineRecord.EventType.RecalculateStyles: >+ case WI.LayoutTimelineRecord.EventType.ForcedLayout: >+ case WI.LayoutTimelineRecord.EventType.Layout: >+ case WI.LayoutTimelineRecord.EventType.Paint: >+ case WI.LayoutTimelineRecord.EventType.Composite: >+ return true; >+ case WI.LayoutTimelineRecord.EventType.InvalidateStyles: >+ case WI.LayoutTimelineRecord.EventType.InvalidateLayout: >+ return false >+ } >+ }); >+ >+ let millisecondStartTime = Math.round(startTime * 1000); >+ let millisecondEndTime = Math.round(endTime * 1000); >+ let millisecondDuration = (millisecondEndTime - millisecondStartTime); >+ >+ let samples = new Array(millisecondDuration); >+ >+ function markRecordEntries(records, callback) { >+ for (let record of records) { >+ let recordStart = Math.round(record.startTime * 1000); >+ let recordEnd = Math.round(record.endTime * 1000); >+ if (recordStart > millisecondEndTime) >+ continue; >+ if (recordEnd < millisecondStartTime) >+ continue; >+ >+ let offset = recordStart - millisecondStartTime; >+ recordStart = Math.max(recordStart, millisecondStartTime); >+ recordEnd = Math.min(recordEnd, millisecondEndTime); >+ >+ let value = callback(record); >+ for (let t = recordStart; t <= recordEnd; ++t) >+ samples[t - millisecondStartTime] = value; >+ } >+ } >+ >+ markRecordEntries(scriptRecords, (record) => { >+ return WI.CPUTimelineView.SampleType.Script; >+ }); >+ >+ markRecordEntries(layoutRecords, (record) => { >+ switch (record.eventType) { >+ case WI.LayoutTimelineRecord.EventType.RecalculateStyles: >+ return WI.CPUTimelineView.SampleType.Style; >+ case WI.LayoutTimelineRecord.EventType.ForcedLayout: >+ case WI.LayoutTimelineRecord.EventType.Layout: >+ return WI.CPUTimelineView.SampleType.Layout; >+ case WI.LayoutTimelineRecord.EventType.Paint: >+ case WI.LayoutTimelineRecord.EventType.Composite: >+ return WI.CPUTimelineView.SampleType.Paint; >+ } >+ }); >+ >+ let samplesIdle = 0; >+ let samplesScript = 0; >+ let samplesLayout = 0; >+ let samplesPaint = 0; >+ let samplesStyle = 0; >+ for (let i = 0; i < samples.length; ++i) { >+ switch (samples[i]) { >+ case undefined: >+ samplesIdle++; >+ break; >+ case WI.CPUTimelineView.SampleType.Script: >+ samplesScript++; >+ break; >+ case WI.CPUTimelineView.SampleType.Layout: >+ samplesLayout++; >+ break; >+ case WI.CPUTimelineView.SampleType.Paint: >+ samplesPaint++; >+ break; >+ case WI.CPUTimelineView.SampleType.Style: >+ samplesStyle++; >+ break; >+ } >+ } >+ >+ return { >+ samples, >+ samplesIdle, >+ samplesScript, >+ samplesLayout, >+ samplesPaint, >+ samplesStyle, >+ }; >+ } >+ >+ _removeWorkerThreadViews() >+ { >+ if (!this._workerViews.length) >+ return; >+ >+ for (let view of this._workerViews) >+ this.removeUnparentedSubview(view); >+ >+ this._workerViews = []; >+ } >+ >+ _clearBreakdownLegend() >+ { >+ this._breakdownLegendScriptElement.textContent = emDash; >+ this._breakdownLegendLayoutElement.textContent = emDash; >+ this._breakdownLegendPaintElement.textContent = emDash; >+ this._breakdownLegendStyleElement.textContent = emDash; >+ >+ this._breakdownChart.centerElement.removeChildren(); >+ } >+ > _cpuTimelineRecordAdded(event) > { > let cpuTimelineRecord = event.data.record; >@@ -196,3 +641,10 @@ WI.CPUTimelineView = class CPUTimelineView extends WI.TimelineView > this.needsLayout(); > } > }; >+ >+WI.CPUTimelineView.SampleType = { >+ Script: "sample-type-script", >+ Layout: "sample-type-layout", >+ Paint: "sample-type-paint", >+ Style: "sample-type-style", >+}; >diff --git a/Source/WebInspectorUI/UserInterface/Views/CPUUsageStackedView.css b/Source/WebInspectorUI/UserInterface/Views/CPUUsageStackedView.css >new file mode 100644 >index 00000000000..48de9673443 >--- /dev/null >+++ b/Source/WebInspectorUI/UserInterface/Views/CPUUsageStackedView.css >@@ -0,0 +1,73 @@ >+/* >+ * 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-stacked-view { >+ display: flex; >+ width: 100%; >+ height: 151px; /* Keep this in sync with cpuUsageViewHeight + 1 (for border-bottom) */ >+ border-bottom: 1px solid var(--border-color); >+} >+ >+.cpu-usage-stacked-view > .details { >+ flex-shrink: 0; >+ width: 150px; >+ padding-top: 10px; >+ font-family: -webkit-system-font, sans-serif; >+ font-size: 12px; >+ color: var(--text-color-secondary); >+ overflow: hidden; >+ text-overflow: ellipsis; >+ -webkit-padding-start: 15px; >+ >+ --cpu-usage-view-details-border-end: 1px solid var(--border-color); >+} >+ >+body[dir=ltr] .cpu-usage-stacked-view > .details { >+ border-right: var(--cpu-usage-view-details-border-end); >+} >+ >+body[dir=rtl] .cpu-usage-stacked-view > .details { >+ border-left: var(--cpu-usage-view-details-border-end); >+} >+ >+.cpu-usage-stacked-view > .details > .name { >+ color: var(--text-color); >+ white-space: nowrap; >+} >+ >+body[dir=rtl] .cpu-usage-stacked-view > .graph { >+ transform: scaleX(-1); >+} >+ >+.cpu-usage-stacked-view > .graph { >+ position: relative; >+} >+ >+.cpu-usage-stacked-view > .graph, >+.cpu-usage-stacked-view > .graph > .stacked-line-chart, >+.cpu-usage-stacked-view > .graph > .stacked-line-chart > svg { >+ width: 100%; >+ height: 100%; >+} >diff --git a/Source/WebInspectorUI/UserInterface/Views/CPUUsageStackedView.js b/Source/WebInspectorUI/UserInterface/Views/CPUUsageStackedView.js >new file mode 100644 >index 00000000000..5dfbe2d0434 >--- /dev/null >+++ b/Source/WebInspectorUI/UserInterface/Views/CPUUsageStackedView.js >@@ -0,0 +1,134 @@ >+/* >+ * 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.CPUUsageStackedView = class CPUUsageStackedView extends WI.View >+{ >+ constructor(displayName) >+ { >+ super(); >+ >+ this.element.classList.add("cpu-usage-stacked-view"); >+ >+ this._detailsElement = this.element.appendChild(document.createElement("div")); >+ this._detailsElement.classList.add("details"); >+ >+ let detailsNameElement = this._detailsElement.appendChild(document.createElement("span")); >+ detailsNameElement.classList.add("name"); >+ detailsNameElement.textContent = displayName; >+ >+ this._detailsElement.appendChild(document.createElement("br")); >+ 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"); >+ >+ this._chart = new WI.StackedLineChart; >+ this.addSubview(this._chart); >+ this._chart.initializeSections(["main-thread-usage", "worker-thread-usage", "total-usage"]); >+ this._graphElement.appendChild(this._chart.element); >+ } >+ >+ // Public >+ >+ get chart() { return this._chart; } >+ >+ clear() >+ { >+ this._cachedAverageSize = undefined; >+ this._cachedMinSize = undefined; >+ this._cachedMaxSize = undefined; >+ this._updateDetails(NaN, NaN); >+ >+ this._chart.clear(); >+ this._chart.needsLayout(); >+ } >+ >+ updateChart(dataPoints, size, visibleEndTime, min, max, average, xScale, yScale) >+ { >+ console.assert(size instanceof WI.Size); >+ console.assert(min >= 0); >+ console.assert(max >= 0); >+ console.assert(min <= max); >+ console.assert(min <= average && average <= max); >+ >+ this._updateDetails(min, max, average); >+ >+ this._chart.clear(); >+ this._chart.size = size; >+ this._chart.needsLayout(); >+ >+ if (!dataPoints.length) >+ return; >+ >+ // Ensure an empty graph is empty. >+ if (!max) >+ return; >+ >+ // Extend the first data point to the start so it doesn't look like we originate at zero size. >+ let firstX = 0; >+ let firstY1 = yScale(dataPoints[0].mainThreadUsage); >+ let firstY2 = yScale(dataPoints[0].mainThreadUsage + dataPoints[0].workerThreadUsage); >+ let firstY3 = yScale(dataPoints[0].usage); >+ this._chart.addPointSet(firstX, [firstY1, firstY2, firstY3]); >+ >+ // Points for data points. >+ for (let dataPoint of dataPoints) { >+ let x = xScale(dataPoint.time); >+ let y1 = yScale(dataPoint.mainThreadUsage); >+ let y2 = yScale(dataPoint.mainThreadUsage + dataPoint.workerThreadUsage); >+ let y3 = yScale(dataPoint.usage) >+ this._chart.addPointSet(x, [y1, y2, y3]); >+ } >+ >+ // Extend the last data point to the end time. >+ let lastDataPoint = dataPoints.lastValue; >+ let lastX = Math.floor(xScale(visibleEndTime)); >+ let lastY1 = yScale(lastDataPoint.mainThreadUsage); >+ let lastY2 = yScale(lastDataPoint.mainThreadUsage + lastDataPoint.workerThreadUsage); >+ let lastY3 = yScale(lastDataPoint.usage); >+ this._chart.addPointSet(lastX, [lastY1, lastY2, lastY3]); >+ } >+ >+ // 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/CPUUsageView.css b/Source/WebInspectorUI/UserInterface/Views/CPUUsageView.css >index ad47598ca4f..a3ce328c33b 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/CPUUsageView.css >+++ b/Source/WebInspectorUI/UserInterface/Views/CPUUsageView.css >@@ -26,7 +26,7 @@ > .cpu-usage-view { > display: flex; > width: 100%; >- height: 76px; /* Keep this in sync with cpuUsageViewHeight + 1 (for border-bottom) */ >+ height: 66px; /* Keep this in sync with threadCPUUsageViewHeight + 1 (for border-bottom) */ > border-bottom: 1px solid var(--border-color); > } > >@@ -37,6 +37,8 @@ > font-family: -webkit-system-font, sans-serif; > font-size: 12px; > color: var(--text-color-secondary); >+ overflow: hidden; >+ text-overflow: ellipsis; > -webkit-padding-start: 15px; > > --cpu-usage-view-details-border-end: 1px solid var(--border-color); >@@ -50,10 +52,19 @@ body[dir=rtl] .cpu-usage-view > .details { > border-left: var(--cpu-usage-view-details-border-end); > } > >+.cpu-usage-view > .details > .name { >+ color: var(--text-color); >+ white-space: nowrap; >+} >+ > body[dir=rtl] .cpu-usage-view > .graph { > transform: scaleX(-1); > } > >+.cpu-usage-view > .graph { >+ position: relative; >+} >+ > .cpu-usage-view > .graph, > .cpu-usage-view > .graph > .line-chart, > .cpu-usage-view > .graph > .line-chart > svg { >diff --git a/Source/WebInspectorUI/UserInterface/Views/CPUUsageView.js b/Source/WebInspectorUI/UserInterface/Views/CPUUsageView.js >index c23aa2ba108..5a8518518ed 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/CPUUsageView.js >+++ b/Source/WebInspectorUI/UserInterface/Views/CPUUsageView.js >@@ -25,7 +25,7 @@ > > WI.CPUUsageView = class CPUUsageView extends WI.View > { >- constructor() >+ constructor(displayName) > { > super(); > >@@ -34,11 +34,14 @@ WI.CPUUsageView = class CPUUsageView extends WI.View > this._detailsElement = this.element.appendChild(document.createElement("div")); > this._detailsElement.classList.add("details"); > >+ let detailsNameElement = this._detailsElement.appendChild(document.createElement("span")); >+ detailsNameElement.classList.add("name"); >+ detailsNameElement.textContent = displayName; >+ >+ this._detailsElement.appendChild(document.createElement("br")); > 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")); >@@ -51,23 +54,25 @@ WI.CPUUsageView = class CPUUsageView extends WI.View > > // Public > >+ get chart() { return this._chart; } >+ > clear() > { > this._cachedAverageSize = undefined; >- this._cachedMinSize = undefined; > this._cachedMaxSize = undefined; > this._updateDetails(NaN, NaN); > > this._chart.clear(); > } > >- updateChart(dataPoints, size, visibleEndTime, min, max, average, xScale, yScale) >+ updateChart(dataPoints, size, visibleEndTime, min, max, average, xScale, yScale, property) > { > console.assert(size instanceof WI.Size); > console.assert(min >= 0); > console.assert(max >= 0); > console.assert(min <= max); > console.assert(min <= average && average <= max); >+ console.assert(property, "CPUUsageView needs a property of the dataPoints to graph"); > > this._updateDetails(min, max, average); > >@@ -84,20 +89,20 @@ WI.CPUUsageView = class CPUUsageView extends WI.View > > // 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); >+ let firstY = yScale(dataPoints[0][property]); > this._chart.addPoint(firstX, firstY); > > // Points for data points. > for (let dataPoint of dataPoints) { > let x = xScale(dataPoint.time); >- let y = yScale(dataPoint.size); >+ let y = yScale(dataPoint[property]); > this._chart.addPoint(x, y); > } > > // Extend the last data point to the end time. > let lastDataPoint = dataPoints.lastValue; > let lastX = Math.floor(xScale(visibleEndTime)); >- let lastY = yScale(lastDataPoint.size); >+ let lastY = yScale(lastDataPoint[property]); > this._chart.addPoint(lastX, lastY); > } > >@@ -105,15 +110,16 @@ WI.CPUUsageView = class CPUUsageView extends WI.View > > _updateDetails(minSize, maxSize, averageSize) > { >- if (this._cachedMinSize === minSize && this._cachedMaxSize === maxSize && this._cachedAverageSize === averageSize) >+ if (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._detailsAverageElement.hidden = !Number.isFinite(averageSize); >+ this._detailsMaxElement.hidden = !Number.isFinite(maxSize); >+ >+ this._detailsAverageElement.textContent = WI.UIString("Average: %s").format(Number.isFinite(averageSize) ? 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/LegacyCPUTimelineView.css b/Source/WebInspectorUI/UserInterface/Views/LegacyCPUTimelineView.css >index a37d9a4a45c..48ad3af3257 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/LegacyCPUTimelineView.css >+++ b/Source/WebInspectorUI/UserInterface/Views/LegacyCPUTimelineView.css >@@ -65,5 +65,5 @@ body[dir=rtl] .timeline-view.legacy-cpu > .content > .details > .timeline-ruler > > .timeline-view.legacy-cpu .cpu-usage-view .line-chart > svg > path { > stroke: var(--cpu-stroke-color); >- fill: var(--cpu-fill-color); >+ fill: var(--cpu-main-thread-fill-color); > } >diff --git a/Source/WebInspectorUI/UserInterface/Views/LegacyCPUTimelineView.js b/Source/WebInspectorUI/UserInterface/Views/LegacyCPUTimelineView.js >index 86b46174aec..7da5662151e 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/LegacyCPUTimelineView.js >+++ b/Source/WebInspectorUI/UserInterface/Views/LegacyCPUTimelineView.js >@@ -105,7 +105,7 @@ WI.LegacyCPUTimelineView = class LegacyCPUTimelineView extends WI.TimelineView > this._timelineRuler.startTime = this.startTime; > this._timelineRuler.endTime = this.endTime; > >- const cpuUsageViewHeight = 75; // Keep this in sync with .legacy-cpu-usage-view >+ const cpuUsageViewHeight = 65; // Keep this in sync with .cpu-usage-view > > let graphStartTime = this.startTime; > let graphEndTime = this.endTime; >@@ -174,7 +174,7 @@ WI.LegacyCPUTimelineView = class LegacyCPUTimelineView extends WI.TimelineView > return size.height - (((value - graphMin) / graphMax) * size.height); > } > >- view.updateChart(dataPoints, size, visibleEndTime, min, max, average, xScale, yScale); >+ view.updateChart(dataPoints, size, visibleEndTime, min, max, average, xScale, yScale, "size"); > } > > layoutView(this._cpuUsageView, {dataPoints, min, max, average}); >diff --git a/Source/WebInspectorUI/UserInterface/Views/MemoryCategoryView.css b/Source/WebInspectorUI/UserInterface/Views/MemoryCategoryView.css >index d5fd3d9ccaf..7a7e766f48f 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/MemoryCategoryView.css >+++ b/Source/WebInspectorUI/UserInterface/Views/MemoryCategoryView.css >@@ -37,6 +37,8 @@ > font-family: -webkit-system-font, sans-serif; > font-size: 12px; > color: var(--text-color-secondary); >+ overflow: hidden; >+ text-overflow: ellipsis; > -webkit-padding-start: 15px; > > --memory-category-view-details-border-end: 1px solid var(--border-color); >@@ -52,9 +54,9 @@ body[dir=rtl] .memory-category-view > .details { > > .memory-category-view > .details > .name { > color: var(--text-color); >+ white-space: nowrap; > } > >- > body[dir=rtl] .memory-category-view > .graph { > transform: scaleX(-1); > } >diff --git a/Source/WebInspectorUI/UserInterface/Views/MemoryTimelineOverviewGraph.js b/Source/WebInspectorUI/UserInterface/Views/MemoryTimelineOverviewGraph.js >index 17e74318fcd..498270dccd0 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/MemoryTimelineOverviewGraph.js >+++ b/Source/WebInspectorUI/UserInterface/Views/MemoryTimelineOverviewGraph.js >@@ -210,8 +210,6 @@ WI.MemoryTimelineOverviewGraph = class MemoryTimelineOverviewGraph extends WI.Ti > this._chart.addPointSet(x, pointSetForRecord(lastRecord)); > } > } >- >- this._chart.updateLayout(); > } > > // Private >diff --git a/Source/WebInspectorUI/UserInterface/Views/MemoryTimelineView.css b/Source/WebInspectorUI/UserInterface/Views/MemoryTimelineView.css >index 060cf3397a9..48f59067fc9 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/MemoryTimelineView.css >+++ b/Source/WebInspectorUI/UserInterface/Views/MemoryTimelineView.css >@@ -23,7 +23,7 @@ > * THE POSSIBILITY OF SUCH DAMAGE. > */ > >-.timeline-view.memory { >+body .timeline-view.memory { > overflow: scroll; > } > >diff --git a/Source/WebInspectorUI/UserInterface/Views/StackedColumnChart.js b/Source/WebInspectorUI/UserInterface/Views/StackedColumnChart.js >new file mode 100644 >index 00000000000..250b8a6d612 >--- /dev/null >+++ b/Source/WebInspectorUI/UserInterface/Views/StackedColumnChart.js >@@ -0,0 +1,126 @@ >+/* >+ * 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. >+ */ >+ >+// StackedColumnChart creates a chart with filled columns each stratified with sections. >+// >+// Initialize the chart with a size. >+// To populate with data, first initialize the sections. The class names you >+// provide for the segments will allow you to style them. You can then include >+// a new set of (x, totalHeight, w, [h1, h2, h3]) points in the chart via `addColumnSet`. >+// The order of `h` values must be in the same order as the sections. >+// The `y` value to be used for each column is `totalHeight - h`. >+// >+// SVG: >+// >+// - There is a single rect for each bar and each section. >+// - Each bar extends all the way down to the bottom, they are layered such >+// that the rects for early sections overlap the sections for later sections. >+// >+// <div class="stacked-column-chart"> >+// <svg viewBox="0 0 800 75"> >+// <rect class="section-class-name-3" width="<w>" height="<h3>" transform="translate(<x>, <y>)" /> >+// <rect class="section-class-name-2" width="<w>" height="<h2>" transform="translate(<x>, <y>)" /> >+// <rect class="section-class-name-1" width="<w>" height="<h1>" transform="translate(<x>, <y>)" /> >+// ... >+// </svg> >+// </div> >+ >+WI.StackedColumnChart = class StackedColumnChart extends WI.View >+{ >+ constructor(size) >+ { >+ super(); >+ >+ this.element.classList.add("stacked-column-chart"); >+ >+ this._svgElement = this.element.appendChild(createSVGElement("svg")); >+ this._svgElement.setAttribute("preserveAspectRatio", "none"); >+ >+ this._sections = null; >+ this._columns = []; >+ this.size = size; >+ } >+ >+ // Public >+ >+ get size() >+ { >+ return this._size; >+ } >+ >+ set size(size) >+ { >+ this._size = size; >+ >+ this._svgElement.setAttribute("viewBox", `0 0 ${size.width} ${size.height}`); >+ } >+ >+ initializeSections(sectionClassNames) >+ { >+ console.assert(this._sections === null, "Should not initialize multiple times"); >+ >+ this._sections = sectionClassNames; >+ } >+ >+ addColumnSet(x, totalHeight, width, heights) >+ { >+ console.assert(heights.length === this._sections.length, "Wrong number of sections in columns set", heights.length, this._sections.length); >+ >+ this._columns.push({x, totalHeight, width, heights}); >+ } >+ >+ clear() >+ { >+ this._columns = []; >+ } >+ >+ // Protected >+ >+ layout() >+ { >+ super.layout(); >+ >+ if (this.layoutReason === WI.View.LayoutReason.Resize) >+ return; >+ >+ this._svgElement.removeChildren(); >+ >+ for (let {x, totalHeight, width, heights} of this._columns) { >+ let sectionIndex = 0; >+ for (let i = heights.length - 1; i >= 0; --i) { >+ let height = heights[i]; >+ // Next rect will be identical, skip this one. >+ if (height === heights[i - 1]) >+ continue; >+ let y = totalHeight - height; >+ let rect = this._svgElement.appendChild(createSVGElement("rect")); >+ rect.classList.add(this._sections[i]); >+ rect.setAttribute("width", width); >+ rect.setAttribute("height", height); >+ rect.setAttribute("transform", `translate(${x}, ${y})`); >+ } >+ } >+ } >+}; >diff --git a/Source/WebInspectorUI/UserInterface/Views/Variables.css b/Source/WebInspectorUI/UserInterface/Views/Variables.css >index 3eac5b98ed3..012e5da0be6 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/Variables.css >+++ b/Source/WebInspectorUI/UserInterface/Views/Variables.css >@@ -126,7 +126,20 @@ > --memory-max-comparison-stroke-color: hsl(220, 10%, 55%); > > --cpu-stroke-color: hsl(118, 33%, 42%); >- --cpu-fill-color: hsl(118, 43%, 55%); >+ --cpu-fill-color: hsl(81, 80%, 50%); >+ --cpu-main-thread-fill-color: hsl(118, 43%, 55%); >+ --cpu-worker-thread-fill-color: hsl(45, 94.75%, 55%); >+ >+ --cpu-idle-fill-color: hsl(220, 10%, 75%); >+ --cpu-idle-stroke-color: hsl(220, 10%, 55%); >+ --cpu-script-fill-color: hsl(269, 65%, 75%); >+ --cpu-script-stroke-color: hsl(269, 33%, 50%); >+ --cpu-style-fill-color: hsl(22, 60%, 70%); >+ --cpu-style-stroke-color: hsl(22, 40%, 50%); >+ --cpu-layout-fill-color: hsl(0, 65%, 75%); >+ --cpu-layout-stroke-color: hsl(0, 54%, 50%); >+ --cpu-paint-fill-color: hsl(76, 49%, 75%); >+ --cpu-paint-stroke-color: hsl(79, 45%, 50%); > > --network-header-color: hsl(204, 52%, 55%); > --network-system-color: hsl(79, 32%, 50%); >diff --git a/Source/WebInspectorUI/UserInterface/Views/View.js b/Source/WebInspectorUI/UserInterface/Views/View.js >index 39bdd8bfbda..abe002c60e8 100644 >--- a/Source/WebInspectorUI/UserInterface/Views/View.js >+++ b/Source/WebInspectorUI/UserInterface/Views/View.js >@@ -131,6 +131,23 @@ WI.View = class View extends WI.Object > view._didMoveToParent(null); > } > >+ removeUnparentedSubview(view) >+ { >+ console.assert(view instanceof WI.View); >+ console.assert(view.element.parentNode !== this._element, "Subview DOM element is expected to be a child of some other element."); >+ >+ let index = this._subviews.lastIndexOf(view); >+ if (index === -1) { >+ console.assert(false, "Cannot remove view which isn't a subview.", view); >+ return; >+ } >+ >+ this._subviews.splice(index, 1); >+ view.element.remove(); >+ >+ view._didMoveToParent(null); >+ } >+ > removeAllSubviews() > { > for (let subview of this._subviews)
You cannot view the attachment while viewing its details because your browser does not support IFRAMEs.
View the attachment on a separate page
.
View Attachment As Diff
View Attachment As Raw
Actions:
View
|
Formatted Diff
|
Diff
Attachments on
bug 194788
:
362324
|
362325
|
362328
|
362542
|
362544
|
362655
|
362801
|
362958