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 = ""; + 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-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.
{ + 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 b8d410bc66543..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,19 @@ @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"); + } + public BuildMetrics getBuildMetrics() { BuildMetrics buildMetrics = new BuildMetrics(); Map buildStepMetrics = buildStepMetrics(); @@ -45,4 +59,9 @@ static class BuildMetrics { public Long duration; public JsonArray records; } + + static class BuildExecutionMetrics { + public List slots; + public Map threadSlotRecords; + } }