From c30e9c159b24acc1dd176c1c7cae694a0c4a46b4 Mon Sep 17 00:00:00 2001 From: Phillip Kruger Date: Mon, 31 Jul 2023 12:26:46 +1000 Subject: [PATCH 1/2] Fix Build Items page Signed-off-by: Phillip Kruger --- .../resources/dev-ui/qwc/qwc-build-items.js | 18 ++++++++++-------- .../build/BuildMetricsJsonRPCService.java | 5 +++++ 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-build-items.js b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-build-items.js index 9e2fddcbcdab6..5c8b7806fca03 100644 --- a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-build-items.js +++ b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-build-items.js @@ -42,7 +42,8 @@ export class QwcBuildItems extends QwcHotReloadElement { }`; static properties = { - _buildStepsMetrics: { state: true }, + _buildItems: { state: true }, + _count: { state: false }, _filtered: {state: true, type: Array} }; @@ -52,14 +53,15 @@ export class QwcBuildItems extends QwcHotReloadElement { } hotReload(){ - this.jsonRpc.getBuildStepsMetrics().then(e => { - this._buildStepsMetrics = e.result; - this._filtered = this._buildStepsMetrics.items; + this.jsonRpc.getBuildItems().then(e => { + this._buildItems = e.result; + this._count = this._buildItems.length; + this._filtered = this._buildItems; }); } render() { - if (this._buildStepsMetrics && this._filtered) { + if (this._buildItems && this._filtered) { return this._render(); }else { return html` @@ -81,18 +83,18 @@ export class QwcBuildItems extends QwcHotReloadElement { _filter(e) { const searchTerm = (e.detail.value || '').trim(); if (searchTerm === '') { - this._filtered = this._buildStepsMetrics.items; + this._filtered = this._buildItems; return; } - this._filtered = this._buildStepsMetrics.items.filter((item) => { + this._filtered = this._buildItems.filter((item) => { return this._match(item.class, searchTerm); }); } _render() { return html`
-
Produced ${this._buildStepsMetrics.itemsCount} build items.
+
Produced ${this._count} build items.
buildStepMetrics = buildStepMetrics(); + return (JsonArray) buildStepMetrics.get("items"); + } + public BuildMetrics getBuildMetrics() { BuildMetrics buildMetrics = new BuildMetrics(); Map buildStepMetrics = buildStepMetrics(); From 1956ac7e84302052cf39fbcab738ebb05c0e8025 Mon Sep 17 00:00:00 2001 From: Phillip Kruger Date: Tue, 1 Aug 2023 22:44:47 +1000 Subject: [PATCH 2/2] Dev UI: Migrate Build Concurrent Execution Graph Signed-off-by: Phillip Kruger --- .../deployment/BuildTimeContentProcessor.java | 2 + .../dev-ui/echarts/echarts-bar-stack.js | 125 ++++++++++++++++++ .../qwc/qwc-build-steps-execution-graph.js | 100 ++++++++++++++ .../resources/dev-ui/qwc/qwc-build-steps.js | 30 ++++- .../build/BuildMetricsJsonRPCService.java | 14 ++ 5 files changed, 267 insertions(+), 4 deletions(-) create mode 100644 extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/echarts/echarts-bar-stack.js create mode 100644 extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-build-steps-execution-graph.js diff --git a/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/BuildTimeContentProcessor.java b/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/BuildTimeContentProcessor.java index 4adf8ef7f499d..a54d6132bd852 100644 --- a/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/BuildTimeContentProcessor.java +++ b/extensions/vertx-http/deployment/src/main/java/io/quarkus/devui/deployment/BuildTimeContentProcessor.java @@ -118,6 +118,8 @@ InternalImportMapBuildItem createKnownInternalImportMap(NonApplicationRootPathBu contextRoot + "echarts/echarts-horizontal-stacked-bar.js"); internalImportMapBuildItem.add("echarts-force-graph", contextRoot + "echarts/echarts-force-graph.js"); + internalImportMapBuildItem.add("echarts-bar-stack", + contextRoot + "echarts/echarts-bar-stack.js"); // Other assets internalImportMapBuildItem.add("icon/", contextRoot + "icon/"); diff --git a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/echarts/echarts-bar-stack.js b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/echarts/echarts-bar-stack.js new file mode 100644 index 0000000000000..9b5ce4fd85d6e --- /dev/null +++ b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/echarts/echarts-bar-stack.js @@ -0,0 +1,125 @@ +import { EchartsAbstractCanvas } from './echarts-abstract-canvas.js'; + +/** + * This wraps the Bar Stack echart into a component + * see https://echarts.apache.org/examples/en/editor.html?c=bar-stack + */ +class EchartsBarStack extends EchartsAbstractCanvas { + + static get properties() { + return { + xdata:{type: String}, + xdataName: {type: String}, + series: { type: String}, + ydataName: {type: String} + }; + } + + constructor() { + super(); + + this.xdata = null; + this.xdataName = null; + this.series = null; + this.primaryTextColor = "--lumo-body-text-color"; + } + + getOption(){ + + let textColor = this.primaryTextColor; + if(textColor.startsWith('--')){ + textColor = getComputedStyle(this.shadowRoot.host).getPropertyValue(textColor); + } + const barStackOption = new Object(); + + barStackOption.tooltip = new Object(); + barStackOption.tooltip.trigger = "item"; + barStackOption.tooltip.axisPointer= new Object(); + barStackOption.tooltip.axisPointer.type = "shadow"; + barStackOption.tooltip.formatter = function (params) { + let namesUL = "
    "; + for (let i = 0; i < params.data.name.length; i++) { + namesUL = namesUL + "
  • " + params.data.name[i] + "
  • "; + } + namesUL = namesUL + "
"; + return ` + ${params.seriesName}
+ ${namesUL}`; + }; + barStackOption.legend = new Object(); + barStackOption.legend.textStyle = new Object(); + barStackOption.legend.textStyle.color = textColor; + + barStackOption.grid = new Object(); + barStackOption.grid.top = "20%"; + barStackOption.grid.left = "3%"; + barStackOption.grid.right = "4%"; + barStackOption.grid.bottom = "3%"; + barStackOption.grid.containLabel = true; + + let xAxis = new Object(); + xAxis.type = "category"; + + xAxis.data = this.xdata.split(','); + xAxis.axisLine = new Object(); + xAxis.axisLine.lineStyle = new Object(); + xAxis.axisLine.lineStyle.color = textColor; + if(this.xdataName){ + xAxis.name = this.xdataName; + xAxis.nameLocation = "center"; + xAxis.nameGap = 30; + } + barStackOption.xAxis = []; + barStackOption.xAxis.push(xAxis); + + let yAxis = new Object(); + yAxis.type = "value"; + yAxis.axisLine = new Object(); + yAxis.axisLine.lineStyle = new Object(); + yAxis.axisLine.lineStyle.color = textColor; + if(this.ydataName){ + yAxis.name = this.ydataName; + yAxis.nameLocation = "center"; + yAxis.nameGap = 30; + } + + barStackOption.yAxis = []; + barStackOption.yAxis.push(yAxis); + + barStackOption.series = []; + let seriesMap = new Map(Object.entries(JSON.parse(this.series))); + + + for (let [key, value] of seriesMap) { + let arr = []; + + for (let i = 0; i < value.length; i++) { + let val = value[i]; + + + let dataItem = new Object(); + dataItem.name = val; + dataItem.label = new Object(); + if(val.length > 0){ + dataItem.value = 1; + } + arr.push(dataItem); + } + + const serie = new Object(); + serie.name = key; + serie.type = "bar"; + serie.stack = "stack"; + serie.emphasis = new Object(); + serie.emphasis.focus = "series"; + + serie.data = arr; + barStackOption.series.push(serie); + } + + return barStackOption; + + } + +} +customElements.define('echarts-bar-stack', EchartsBarStack); \ No newline at end of file diff --git a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-build-steps-execution-graph.js b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-build-steps-execution-graph.js new file mode 100644 index 0000000000000..671562768e232 --- /dev/null +++ b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-build-steps-execution-graph.js @@ -0,0 +1,100 @@ +import { LitElement, html, css} from 'lit'; +import { JsonRpc } from 'jsonrpc'; +import 'echarts-bar-stack'; +import '@vaadin/button'; +import '@vaadin/checkbox'; +import '@vaadin/checkbox-group'; +import '@vaadin/progress-bar'; + +/** + * This component shows the Build Step Execution Graph + */ +export class QwcBuildStepsExecutionGraph extends LitElement { + + static styles = css` + .top-bar { + display: flex; + align-items: baseline; + gap: 20px; + padding-left: 20px; + padding-right: 20px; + } + + .top-bar h4 { + color: var(--lumo-contrast-60pct); + } + `; + + static properties = { + extensionName: {type: String}, // TODO: Add 'pane' concept in router to register internal extension pages. + _threadSlotRecords: {state: true}, + _slots: {state: true} + }; + + constructor() { + super(); + this._threadSlotRecords = null; + this._slots = null; + } + + connectedCallback() { + super.connectedCallback(); + this.jsonRpc = new JsonRpc(this.extensionName); + this._fetchBuildStepsExecutionData(); + } + + _fetchBuildStepsExecutionData(){ + this.jsonRpc.getThreadSlotRecords().then(jsonRpcResponse => { + this._slots = jsonRpcResponse.result.slots; + this._threadSlotRecords = jsonRpcResponse.result.threadSlotRecords; + }); + } + + render() { + + if(this._threadSlotRecords){ + let xdata = this._slots.toString(); + let xname = this._slots.length + " time slots (" + this._slots[0] +" ms)"; + let yname = "Number of build threads used in a time slot"; + return html`${this._renderTopBar()} + + + `; + }else{ + return html` +
+
Loading Build Steps Execution Graph...
+ +
+ `; + } + + + } + + _renderTopBar(){ + return html` +
+ + + Back + +

Build Steps Concurrent Execution Chart

+
`; + } + + _backAction(){ + const back = new CustomEvent("build-steps-graph-back", { + detail: {}, + bubbles: true, + cancelable: true, + composed: false, + }); + this.dispatchEvent(back); + } +} +customElements.define('qwc-build-steps-execution-graph', QwcBuildStepsExecutionGraph); \ No newline at end of file diff --git a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-build-steps.js b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-build-steps.js index 77c6faa9c6e04..77a1365849ff6 100644 --- a/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-build-steps.js +++ b/extensions/vertx-http/dev-ui-resources/src/main/resources/dev-ui/qwc/qwc-build-steps.js @@ -9,8 +9,9 @@ import '@vaadin/text-field'; import '@vaadin/vertical-layout'; import '@vaadin/horizontal-layout'; import '@vaadin/progress-bar'; +import '@vaadin/button'; import './qwc-build-step-graph.js'; - +import './qwc-build-steps-execution-graph.js'; /** * This component shows the Build Steps @@ -57,6 +58,7 @@ export class QwcBuildSteps extends QwcHotReloadElement { static properties = { _buildMetrics: { state: true }, _selectedBuildStep: {state: true}, + _showBuildStepsExecutionGraph: {state: true}, _filtered: {state: true, type: Array} }; @@ -64,6 +66,7 @@ export class QwcBuildSteps extends QwcHotReloadElement { super(); this._buildMetrics = null; this._selectedBuildStep = null; + this._showBuildStepsExecutionGraph = false; this.hotReload(); } @@ -109,7 +112,9 @@ export class QwcBuildSteps extends QwcHotReloadElement { _render() { if(this._selectedBuildStep){ return this._renderBuildStepGraph(); - }else{ + }else if(this._showBuildStepsExecutionGraph){ + return this._renderBuildStepsExecutionGraph(); + }else{ return this._renderBuildStepList(); } } @@ -117,7 +122,13 @@ export class QwcBuildSteps extends QwcHotReloadElement { _renderBuildStepList(){ return html`
-
Executed ${this._buildMetrics.records.length} build steps on ${this._buildMetrics.numberOfThreads} threads in ${this._buildMetrics.duration} ms.
+
+ Executed ${this._buildMetrics.records.length} build steps on ${this._buildMetrics.numberOfThreads} threads in ${this._buildMetrics.duration} ms. + + + Build Steps Concurrent Execution Chart + +
`; + } + _stepIdRenderer(record) { return html`${record.stepId}`; } @@ -175,11 +191,17 @@ export class QwcBuildSteps extends QwcHotReloadElement { _showGraph(buildStep){ this._selectedBuildStep = buildStep; + this._showBuildStepsExecutionGraph = false; } _showBuildStepsList(){ this._selectedBuildStep = null; + this._showBuildStepsExecutionGraph = false; } + _showBuildStepsChart(){ + this._selectedBuildStep = null; + this._showBuildStepsExecutionGraph = true; + } } customElements.define('qwc-build-steps', QwcBuildSteps); \ No newline at end of file diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/build/BuildMetricsJsonRPCService.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/build/BuildMetricsJsonRPCService.java index ef7ae2ebee025..14be5c34a0b7b 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/build/BuildMetricsJsonRPCService.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/devui/runtime/build/BuildMetricsJsonRPCService.java @@ -1,5 +1,6 @@ package io.quarkus.devui.runtime.build; +import java.util.List; import java.util.Map; import jakarta.enterprise.context.ApplicationScoped; @@ -10,6 +11,14 @@ @ApplicationScoped public class BuildMetricsJsonRPCService { + public BuildExecutionMetrics getThreadSlotRecords() { + BuildExecutionMetrics buildExecutionMetrics = new BuildExecutionMetrics(); + Map buildStepMetrics = buildStepMetrics(); + buildExecutionMetrics.threadSlotRecords = (Map) buildStepMetrics.get("threadSlotRecords"); + buildExecutionMetrics.slots = (List) buildStepMetrics.get("slots"); + return buildExecutionMetrics; + } + public JsonArray getBuildItems() { Map buildStepMetrics = buildStepMetrics(); return (JsonArray) buildStepMetrics.get("items"); @@ -50,4 +59,9 @@ static class BuildMetrics { public Long duration; public JsonArray records; } + + static class BuildExecutionMetrics { + public List slots; + public Map threadSlotRecords; + } }