From becdd722cd37abe62d9167a9e70664e4d1c3534d Mon Sep 17 00:00:00 2001 From: Martin Kouba Date: Thu, 4 Dec 2025 08:47:38 +0100 Subject: [PATCH] Build metrics: make it possible to collect produced items - if quarkus.builder.metrics.extended-capture=true - show the produced build items in the Dev UI - do not collect any metrics unless quarkus.builder.metrics.enabled=true - in the dev mode, collect the metrics by default unless quarkus.builder.metrics.enabled=false - deprecate quarkus.debug.dump-build-metrics --- TROUBLESHOOTING.md | 8 +- .../java/io/quarkus/builder/BuildContext.java | 2 +- .../java/io/quarkus/builder/BuildMetrics.java | 203 ++++++++++++------ .../io/quarkus/deployment/DebugConfig.java | 3 + .../quarkus/deployment/QuarkusAugmentor.java | 21 +- .../io/quarkus/runtime/BuilderConfig.java | 23 ++ .../resources/dev-ui/qwc/qwc-build-items.js | 69 +++++- .../resources/dev-ui/qwc/qwc-build-steps.js | 56 ++++- .../build/BuildMetricsDevUIController.java | 40 +++- .../build/BuildMetricsJsonRPCService.java | 13 +- 10 files changed, 348 insertions(+), 90 deletions(-) diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md index 9f5d943708ec2..aa3e40ae95c9c 100644 --- a/TROUBLESHOOTING.md +++ b/TROUBLESHOOTING.md @@ -204,10 +204,14 @@ This can be achieved by adding the following system property: `-Dquarkus.debug.p There is also a nice visualization of build steps available in the Dev UI located here: . -If you want to have the same visualization of build steps processing when building your application, you can use the `quarkus.debug.dump-build-metrics=true` property. -For example using `mvn package -Dquarkus.debug.dump-build-metrics=true`, will generate a `build-metrics.json` in your `target` repository that you can process via the quarkus-build-report application available here . +If you want to have a similar visualization of build steps processing when building your application, you can use the `quarkus.builder.metrics.enabled` property. +For example using `mvn package -Dquarkus.builder.metrics.enabled=true`, will generate a `build-metrics.json` in your `target` repository. +The generated file can be processed with the `quarkus-build-report` application available here . This application will generate a `report.html` that you can open in your browser. +There is also the `quarkus.builder.metrics.extended-capture` config property. +If set to `true` then the collection of metrics is enhanced but the size of the generated JSON file may grow significantly. + ## What about Windows? If you are on Windows, you can still get useful performance insights using JFR - Java Flight Recorder. diff --git a/core/builder/src/main/java/io/quarkus/builder/BuildContext.java b/core/builder/src/main/java/io/quarkus/builder/BuildContext.java index bb2645fd16bb0..f193b4d8daade 100644 --- a/core/builder/src/main/java/io/quarkus/builder/BuildContext.java +++ b/core/builder/src/main/java/io/quarkus/builder/BuildContext.java @@ -227,7 +227,7 @@ private void doProduce(ItemId id, BuildItem value) { throw Messages.msg.cannotMulti(id); } } - execution.getMetrics().buildItemProduced(value); + execution.getMetrics().buildItemProduced(stepInfo, value); } void depFinished() { diff --git a/core/builder/src/main/java/io/quarkus/builder/BuildMetrics.java b/core/builder/src/main/java/io/quarkus/builder/BuildMetrics.java index e7c6a45d4b14f..83b01cf257e20 100644 --- a/core/builder/src/main/java/io/quarkus/builder/BuildMetrics.java +++ b/core/builder/src/main/java/io/quarkus/builder/BuildMetrics.java @@ -13,10 +13,13 @@ import java.util.Collection; import java.util.Comparator; import java.util.List; +import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; +import java.util.stream.Collectors; import org.jboss.logging.Logger; @@ -26,18 +29,43 @@ public class BuildMetrics { + public static final String BUILDER_METRICS_ENABLED = "quarkus.builder.metrics.enabled"; + public static final String BUILDER_METRICS_EXTENDED_CAPTURE = "quarkus.builder.metrics.extended-capture"; + static final Logger LOG = Logger.getLogger(BuildMetrics.class.getName()); private volatile LocalDateTime started; private volatile long duration; private final String buildTargetName; - private final ConcurrentMap records = new ConcurrentHashMap<>(); - private final ConcurrentMap buildItems = new ConcurrentHashMap<>(); + // build step id -> record + private final ConcurrentMap records; + // build item class -> count + private final ConcurrentMap buildItems; + // build step id -> produced build items + private final ConcurrentMap> buildItemsExtended; private final AtomicInteger idGenerator; public BuildMetrics(String buildTargetName) { + boolean enabled = Boolean.getBoolean(BUILDER_METRICS_ENABLED) + // This system property is deprecated and will be removed + || Boolean.getBoolean("quarkus.debug.dump-build-metrics"); this.buildTargetName = buildTargetName; - this.idGenerator = new AtomicInteger(); + if (enabled) { + this.idGenerator = new AtomicInteger(); + this.records = new ConcurrentHashMap<>(); + if (Boolean.getBoolean(BUILDER_METRICS_EXTENDED_CAPTURE)) { + this.buildItemsExtended = new ConcurrentHashMap<>(); + this.buildItems = null; + } else { + this.buildItemsExtended = null; + this.buildItems = new ConcurrentHashMap<>(); + } + } else { + this.idGenerator = null; + this.records = null; + this.buildItemsExtended = null; + this.buildItems = null; + } } public Collection getRecords() { @@ -53,82 +81,127 @@ public void buildFinished(long duration) { } public void buildStepFinished(StepInfo stepInfo, String thread, LocalTime started, long duration) { - records.put(stepInfo.getBuildStep().getId(), - new BuildStepRecord(idGenerator.incrementAndGet(), stepInfo, thread, started, duration)); + if (enabled()) { + records.put(stepInfo.getBuildStep().getId(), + new BuildStepRecord(idGenerator.incrementAndGet(), stepInfo, thread, started, duration)); + } } - public void buildItemProduced(BuildItem buildItem) { - buildItems.compute(buildItem.getClass().getName(), this::itemProduced); + public void buildItemProduced(StepInfo stepInfo, BuildItem buildItem) { + if (enabled()) { + if (buildItems != null) { + buildItems.compute(buildItem.getClass().getName(), this::itemProduced); + } else { + buildItemsExtended.compute(stepInfo.getBuildStep().getId(), (key, list) -> { + String buildItemClass = buildItem.getClass().getName(); + if (list == null) { + list = new ArrayList<>(); + } + list.add(buildItemClass); + return list; + }); + } + } } - private Integer itemProduced(String key, Integer val) { - if (val == null) { - return 1; - } - return val + 1; + private Long itemProduced(String key, Long val) { + return val == null ? 1 : val + 1; } public void dumpTo(Path file) throws IOException { - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss.SSS"); + if (enabled()) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss.SSS"); + + List sortedSteps = new ArrayList<>(records.values()); + sortedSteps.sort(new Comparator() { + @Override + public int compare(BuildStepRecord o1, BuildStepRecord o2) { + return Long.compare(o2.duration, o1.duration); + } + }); + + JsonObjectBuilder json = Json.object(); + json.put("buildTarget", buildTargetName); + json.put("started", started.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); + json.put("duration", duration); + + JsonArrayBuilder steps = Json.array(); + json.put("records", steps); + for (BuildStepRecord rec : sortedSteps) { + JsonObjectBuilder recObject = Json.object(); + recObject.put("id", rec.id); + recObject.put("stepId", rec.stepInfo.getBuildStep().getId()); + recObject.put("thread", rec.thread); + recObject.put("started", rec.started.format(formatter)); + recObject.put("duration", rec.duration); + JsonArrayBuilder dependentsArray = Json.array(); + for (StepInfo dependent : rec.stepInfo.getDependents()) { + BuildStepRecord dependentRecord = records.get(dependent.getBuildStep().getId()); + if (dependentRecord != null) { + dependentsArray.add(dependentRecord.id); + } else { + LOG.warnf("Dependent record not found for stepId: %s", dependent.getBuildStep().getId()); + } + } + recObject.put("dependents", dependentsArray); + if (buildItemsExtended != null) { + JsonArrayBuilder producedItems = Json.array(); + List items = buildItemsExtended.get(rec.stepInfo.getBuildStep().getId()); + if (items != null) { + // build item class -> count + Map counts = items + .stream() + .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())); + List> sortedItems = new ArrayList<>(counts.entrySet()); + sortedItems.sort(this::compareBuildItems); + for (Entry e : sortedItems) { + producedItems.add(Json.object() + .put("item", e.getKey()) + .put("count", e.getValue())); + } + recObject.put("producedItems", producedItems); + } + } + steps.add(recObject); + } - List sortedSteps = new ArrayList<>(records.values()); - sortedSteps.sort(new Comparator() { - @Override - public int compare(BuildStepRecord o1, BuildStepRecord o2) { - return Long.compare(o2.duration, o1.duration); + List> sortedItems; + if (buildItemsExtended != null) { + // build item class -> count + Map counts = buildItemsExtended.values() + .stream() + .flatMap(List::stream) + .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())); + sortedItems = new ArrayList<>(counts.entrySet()); + } else { + sortedItems = new ArrayList<>(buildItems.size()); + buildItems.entrySet().forEach(sortedItems::add); } - }); - - JsonObjectBuilder json = Json.object(); - json.put("buildTarget", buildTargetName); - json.put("started", started.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)); - json.put("duration", duration); - - JsonArrayBuilder steps = Json.array(); - json.put("records", steps); - for (BuildStepRecord rec : sortedSteps) { - JsonObjectBuilder recObject = Json.object(); - recObject.put("id", rec.id); - recObject.put("stepId", rec.stepInfo.getBuildStep().getId()); - recObject.put("thread", rec.thread); - recObject.put("started", rec.started.format(formatter)); - recObject.put("duration", rec.duration); - JsonArrayBuilder dependentsArray = Json.array(); - for (StepInfo dependent : rec.stepInfo.getDependents()) { - BuildStepRecord dependentRecord = records.get(dependent.getBuildStep().getId()); - if (dependentRecord != null) { - dependentsArray.add(dependentRecord.id); - } else { - LOG.warnf("Dependent record not found for stepId: %s", dependent.getBuildStep().getId()); - } + sortedItems.sort(this::compareBuildItems); + JsonArrayBuilder items = Json.array(); + json.put("items", items); + long itemsCount = 0; + for (Entry e : sortedItems) { + JsonObjectBuilder itemObject = Json.object(); + itemObject.put("class", e.getKey()); + itemObject.put("count", e.getValue()); + items.add(itemObject); + itemsCount += e.getValue(); } - recObject.put("dependents", dependentsArray); - steps.add(recObject); - } + json.put("itemsCount", itemsCount); - List> sortedItems = new ArrayList<>(buildItems.size()); - buildItems.entrySet().forEach(sortedItems::add); - sortedItems.sort(new Comparator>() { - @Override - public int compare(Entry o1, Entry o2) { - return Integer.compare(o2.getValue(), o1.getValue()); + try (BufferedWriter writer = new BufferedWriter(new FileWriter(file.toFile(), StandardCharsets.UTF_8))) { + json.appendTo(writer); } - }); - JsonArrayBuilder items = Json.array(); - json.put("items", items); - Integer itemsCount = 0; - for (Entry e : sortedItems) { - JsonObjectBuilder itemObject = Json.object(); - itemObject.put("class", e.getKey()); - itemObject.put("count", e.getValue()); - items.add(itemObject); - itemsCount += e.getValue(); } - json.put("itemsCount", itemsCount); + } - try (BufferedWriter writer = new BufferedWriter(new FileWriter(file.toFile(), StandardCharsets.UTF_8))) { - json.appendTo(writer); - } + private boolean enabled() { + return records != null; + } + + private int compareBuildItems(Entry o1, Entry o2) { + return Long.compare(o2.getValue(), o1.getValue()); } public static class BuildStepRecord { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/DebugConfig.java b/core/deployment/src/main/java/io/quarkus/deployment/DebugConfig.java index 2006206f2fb97..2cea25801dfc7 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/DebugConfig.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/DebugConfig.java @@ -44,7 +44,10 @@ public interface DebugConfig { /** * If set to true then dump the build metrics to a JSON file in the build directory. + * + * @deprecated Use {@link io.quarkus.runtime.BuilderConfig#Metrics()} instead. */ + @Deprecated(forRemoval = true, since = "3.31") @WithDefault("false") boolean dumpBuildMetrics(); } diff --git a/core/deployment/src/main/java/io/quarkus/deployment/QuarkusAugmentor.java b/core/deployment/src/main/java/io/quarkus/deployment/QuarkusAugmentor.java index 68bab83adea98..94c7608508fd1 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/QuarkusAugmentor.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/QuarkusAugmentor.java @@ -21,6 +21,7 @@ import io.quarkus.builder.BuildChain; import io.quarkus.builder.BuildChainBuilder; import io.quarkus.builder.BuildExecutionBuilder; +import io.quarkus.builder.BuildMetrics; import io.quarkus.builder.BuildResult; import io.quarkus.builder.item.BuildItem; import io.quarkus.deployment.builditem.AdditionalApplicationArchiveBuildItem; @@ -132,6 +133,14 @@ public BuildResult run() throws Exception { if (launchMode.isDevOrTest()) { chainBuilder.addFinal(RuntimeApplicationShutdownBuildItem.class); } + if (System.getProperty(BuildMetrics.BUILDER_METRICS_ENABLED) == null + && launchMode.isDev() + && !launchMode.isRemoteDev()) { + // If quarkus.builder.metrics.enabled is not set then + // collect build metrics in dev mode but not in remote-dev + // (as it could cause issues with container permissions) + System.setProperty("quarkus.builder.metrics.enabled", "true"); + } final ArchiveRootBuildItem.Builder rootBuilder = ArchiveRootBuildItem.builder(); if (root != null) { @@ -162,18 +171,16 @@ public BuildResult run() throws Exception { + "ms"; if (launchMode.isProduction()) { log.info(message); - if (Boolean.parseBoolean(System.getProperty("quarkus.debug.dump-build-metrics"))) { - buildResult.getMetrics().dumpTo(targetDir.resolve("build-metrics.json")); - } } else { //test and dev mode already report the total startup time, no need to add noise to the logs log.debug(message); + } - // Dump the metrics in the dev mode but not remote-dev (as it could cause issues with container permissions) - if (launchMode.isDev() && !launchMode.isRemoteDev()) { - buildResult.getMetrics().dumpTo(targetDir.resolve("build-metrics.json")); - } + // If enabled then dump build metrics to a JSON file in the build directory + if (targetDir != null) { + buildResult.getMetrics().dumpTo(targetDir.resolve("build-metrics.json")); } + return buildResult; } finally { try { diff --git a/core/runtime/src/main/java/io/quarkus/runtime/BuilderConfig.java b/core/runtime/src/main/java/io/quarkus/runtime/BuilderConfig.java index 95a4b5ce976a2..073a06692e04d 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/BuilderConfig.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/BuilderConfig.java @@ -5,6 +5,7 @@ import io.quarkus.runtime.annotations.ConfigPhase; import io.quarkus.runtime.annotations.ConfigRoot; import io.smallrye.config.ConfigMapping; +import io.smallrye.config.WithDefault; /** * Builder. @@ -26,4 +27,26 @@ public interface BuilderConfig { * Whether to log the cause of a conflict. */ Optional logConflictCause(); + + /** + * Build metrics configuration. + */ + Metrics Metrics(); + + interface Metrics { + + /** + * If set to true then dump the build metrics to a JSON file in the build directory. + */ + @WithDefault("false") + boolean enabled(); + + /** + * If set to true then the collection of metrics is enhanced but the size of the generated JSON file may grow + * significantly. + */ + @WithDefault("false") + boolean extendedCapture(); + + } } diff --git a/extensions/devui/resources/src/main/resources/dev-ui/qwc/qwc-build-items.js b/extensions/devui/resources/src/main/resources/dev-ui/qwc/qwc-build-items.js index 42fdb8cec7501..af8c31d76dde7 100644 --- a/extensions/devui/resources/src/main/resources/dev-ui/qwc/qwc-build-items.js +++ b/extensions/devui/resources/src/main/resources/dev-ui/qwc/qwc-build-items.js @@ -2,7 +2,7 @@ import { QwcHotReloadElement, html, css} from 'qwc-hot-reload-element'; import { JsonRpc } from 'jsonrpc'; import '@vaadin/grid'; -import { columnBodyRenderer } from '@vaadin/grid/lit.js'; +import { columnBodyRenderer, gridRowDetailsRenderer } from '@vaadin/grid/lit.js'; import '@vaadin/grid/vaadin-grid-sort-column.js'; import '@vaadin/icon'; import '@vaadin/text-field'; @@ -41,12 +41,24 @@ export class QwcBuildItems extends QwcHotReloadElement { .datatable { width: 100%; - }`; + } + + .build-item-detail { + padding: 1em; + } + + .caption { + font-weight: bold; + } + + `; static properties = { + _enabled: {state: true}, _buildItems: { state: true }, _count: { state: false }, - _filtered: {state: true, type: Array} + _filtered: {state: true, type: Array}, + _detailsOpenedItem: {state: true, type: Array} }; constructor() { @@ -57,6 +69,7 @@ export class QwcBuildItems extends QwcHotReloadElement { hotReload(){ this.jsonRpc.getBuildItems().then(e => { + this._enabled = e.result.enabled; this._buildItems = e.result.items; this._count = e.result.itemsCount; this._filtered = this._buildItems; @@ -64,9 +77,13 @@ export class QwcBuildItems extends QwcHotReloadElement { } render() { - if (this._buildItems && this._filtered) { + if (!this._enabled) { + return html` + Build metrics not enabled. + `; + } else if (this._buildItems && this._filtered) { return this._render(); - }else { + } else { return html`
${msg('Loading build items...', { id: 'buildmetrics-loading-items' })}
@@ -109,7 +126,15 @@ export class QwcBuildItems extends QwcHotReloadElement { @value-changed="${(e) => this._filter(e)}"> - +
`; } + + _buildItemDetailRenderer(item) { + if (!item.topProducers) { + return html`
+ Top producers not collected. Use -Dquarkus.builder.metrics.extended-capture=true to get more information. +
` + } + return html` +
+
Top 10 producers out of ${item.totalProducers} total
+ + + + + + +
+ ` + } + + _renderBuildStep(producer) { + return html`${producer.stepId}`; + } + _classRenderer(item) { return html`${item.class}`; } + } customElements.define('qwc-build-items', QwcBuildItems); \ No newline at end of file diff --git a/extensions/devui/resources/src/main/resources/dev-ui/qwc/qwc-build-steps.js b/extensions/devui/resources/src/main/resources/dev-ui/qwc/qwc-build-steps.js index e83f9c12db92f..8e245c65f45b6 100644 --- a/extensions/devui/resources/src/main/resources/dev-ui/qwc/qwc-build-steps.js +++ b/extensions/devui/resources/src/main/resources/dev-ui/qwc/qwc-build-steps.js @@ -2,7 +2,7 @@ import { QwcHotReloadElement, html, css} from 'qwc-hot-reload-element'; import { JsonRpc } from 'jsonrpc'; import '@vaadin/grid'; -import { columnBodyRenderer } from '@vaadin/grid/lit.js'; +import { columnBodyRenderer, gridRowDetailsRenderer } from '@vaadin/grid/lit.js'; import '@vaadin/grid/vaadin-grid-sort-column.js'; import '@vaadin/icon'; import '@vaadin/text-field'; @@ -57,13 +57,17 @@ export class QwcBuildSteps extends QwcHotReloadElement { overflow: hidden; height: 100%; } + .build-step-detail { + padding: 1em; + } `; static properties = { _buildMetrics: { state: true }, _selectedBuildStep: {state: true}, _showBuildStepsExecutionGraph: {state: true}, - _filtered: {state: true, type: Array} + _filtered: {state: true, type: Array}, + _detailsOpenedItem: {state: true, type: Array} }; constructor() { @@ -71,6 +75,7 @@ export class QwcBuildSteps extends QwcHotReloadElement { updateWhenLocaleChanges(this); this._buildMetrics = null; this._selectedBuildStep = null; + this._detailsOpenedItem = []; this._showBuildStepsExecutionGraph = false; this.hotReload(); } @@ -83,9 +88,13 @@ export class QwcBuildSteps extends QwcHotReloadElement { } render() { - if (this._buildMetrics && this._filtered) { + if (this._buildMetrics && !this._buildMetrics.enabled) { + return html` + Build metrics not enabled. + `; + } else if (this._buildMetrics && this._filtered) { return this._render(); - }else { + } else { return html`
${msg('Loading build steps...', { id: 'buildmetrics-loading-steps' })}
@@ -149,7 +158,16 @@ export class QwcBuildSteps extends QwcHotReloadElement { @value-changed="${(e) => this._filter(e)}"> - +
`; } + + _buildStepDetailRenderer(buildStep) { + if (!buildStep.producedItems) { + return html`
+ Produced build items not collected. Use -Dquarkus.builder.metrics.extended-capture=true to get more information. +
` + } + return html`
+ + + + + + +
+ ` + } + + _renderBuildItem(record) { + return html`${record.item}`; + } _renderBuildStepGraph(){ return html` prepareBuildStepsMetrics() { Map stepIdToRecord = new HashMap<>(); Map recordIdToRecord = new HashMap<>(); Map> threadToRecords = new HashMap<>(); + Map> itemToSteps = new HashMap<>(); long buildDuration = 0; LocalTime buildStarted = null; @@ -80,10 +81,11 @@ public Map prepareBuildStepsMetrics() { JsonArray records = data.getJsonArray("records"); for (Object record : records) { JsonObject recordObj = (JsonObject) record; + String stepId = recordObj.getString("stepId"); recordObj.put("encodedStepId", URLEncoder.encode(recordObj.getString("stepId"), StandardCharsets.UTF_8.toString())); String thread = recordObj.getString("thread"); - stepIdToRecord.put(recordObj.getString("stepId"), recordObj); + stepIdToRecord.put(stepId, recordObj); recordIdToRecord.put(recordObj.getInteger("id"), recordObj); List steps = threadToRecords.get(thread); if (steps == null) { @@ -91,10 +93,40 @@ public Map prepareBuildStepsMetrics() { threadToRecords.put(thread, steps); } steps.add(recordObj); + JsonArray producedItems = recordObj.getJsonArray("producedItems"); + if (producedItems != null) { + for (int i = 0; i < producedItems.size(); i++) { + JsonObject r = producedItems.getJsonObject(i); + String item = r.getString("item"); + long count = r.getLong("count"); + List producers = itemToSteps.get(item); + if (producers == null) { + producers = new ArrayList<>(); + itemToSteps.put(item, producers); + } + producers.add(new JsonObject() + .put("stepId", stepId) + .put("count", count)); + } + } + } + + JsonArray items = data.getJsonArray("items"); + for (int i = 0; i < items.size(); i++) { + JsonObject item = items.getJsonObject(i); + List producers = itemToSteps.get(item.getString("class")); + if (producers != null) { + List topProducers = producers.stream() + .sorted(this::compareProducer) + .limit(10) + .toList(); + item.put("topProducers", topProducers); + item.put("totalProducers", producers.size()); + } } metrics.put("records", records); - metrics.put("items", data.getJsonArray("items")); + metrics.put("items", items); metrics.put("itemsCount", data.getInteger("itemsCount")); metrics.put("duration", buildDuration); } catch (IOException e) { @@ -149,6 +181,10 @@ public Map prepareBuildStepsMetrics() { return metrics; } + private int compareProducer(JsonObject o1, JsonObject o2) { + return Long.compare(o2.getLong("count"), o1.getLong("count")); + } + DependencyGraph buildDependencyGraph(JsonObject step, Map stepIdToRecord, Map recordIdToRecord) { Set nodes = new HashSet<>(); diff --git a/extensions/devui/runtime/src/main/java/io/quarkus/devui/runtime/build/BuildMetricsJsonRPCService.java b/extensions/devui/runtime/src/main/java/io/quarkus/devui/runtime/build/BuildMetricsJsonRPCService.java index 6dcdaa24bd2e8..a9419e401c1e5 100644 --- a/extensions/devui/runtime/src/main/java/io/quarkus/devui/runtime/build/BuildMetricsJsonRPCService.java +++ b/extensions/devui/runtime/src/main/java/io/quarkus/devui/runtime/build/BuildMetricsJsonRPCService.java @@ -22,8 +22,14 @@ public BuildExecutionMetrics getThreadSlotRecords() { public BuildItems getBuildItems() { Map buildStepMetrics = buildStepMetrics(); BuildItems buildItems = new BuildItems(); - buildItems.items = (JsonArray) buildStepMetrics.get("items"); - buildItems.itemsCount = (int) buildStepMetrics.get("itemsCount"); + JsonArray items = (JsonArray) buildStepMetrics.get("items"); + if (items != null) { + buildItems.items = items; + buildItems.itemsCount = (int) buildStepMetrics.get("itemsCount"); + buildItems.enabled = true; + } else { + buildItems.enabled = false; + } return buildItems; } @@ -38,6 +44,7 @@ public BuildMetrics getBuildMetrics() { buildMetrics.numberOfThreads = threadSlotRecords.size(); buildMetrics.duration = duration; buildMetrics.records = records; + buildMetrics.enabled = records != null; return buildMetrics; } @@ -58,12 +65,14 @@ private Map buildStepMetrics() { } static class BuildMetrics { + public boolean enabled; public int numberOfThreads; public Long duration; public JsonArray records; } static class BuildItems { + public boolean enabled; public int itemsCount; public JsonArray items; }