Skip to content

Commit becdd72

Browse files
committed
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
1 parent 26c975f commit becdd72

File tree

10 files changed

+348
-90
lines changed

10 files changed

+348
-90
lines changed

TROUBLESHOOTING.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,10 +204,14 @@ This can be achieved by adding the following system property: `-Dquarkus.debug.p
204204

205205
There is also a nice visualization of build steps available in the Dev UI located here: <http://localhost:8080/q/dev/build-steps>.
206206

207-
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.
208-
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 <https://github.com/mkouba/quarkus-build-report>.
207+
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.
208+
For example using `mvn package -Dquarkus.builder.metrics.enabled=true`, will generate a `build-metrics.json` in your `target` repository.
209+
The generated file can be processed with the `quarkus-build-report` application available here <https://github.com/mkouba/quarkus-build-report>.
209210
This application will generate a `report.html` that you can open in your browser.
210211

212+
There is also the `quarkus.builder.metrics.extended-capture` config property.
213+
If set to `true` then the collection of metrics is enhanced but the size of the generated JSON file may grow significantly.
214+
211215
## What about Windows?
212216

213217
If you are on Windows, you can still get useful performance insights using JFR - Java Flight Recorder.

core/builder/src/main/java/io/quarkus/builder/BuildContext.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ private void doProduce(ItemId id, BuildItem value) {
227227
throw Messages.msg.cannotMulti(id);
228228
}
229229
}
230-
execution.getMetrics().buildItemProduced(value);
230+
execution.getMetrics().buildItemProduced(stepInfo, value);
231231
}
232232

233233
void depFinished() {

core/builder/src/main/java/io/quarkus/builder/BuildMetrics.java

Lines changed: 138 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,13 @@
1313
import java.util.Collection;
1414
import java.util.Comparator;
1515
import java.util.List;
16+
import java.util.Map;
1617
import java.util.Map.Entry;
1718
import java.util.concurrent.ConcurrentHashMap;
1819
import java.util.concurrent.ConcurrentMap;
1920
import java.util.concurrent.atomic.AtomicInteger;
21+
import java.util.function.Function;
22+
import java.util.stream.Collectors;
2023

2124
import org.jboss.logging.Logger;
2225

@@ -26,18 +29,43 @@
2629

2730
public class BuildMetrics {
2831

32+
public static final String BUILDER_METRICS_ENABLED = "quarkus.builder.metrics.enabled";
33+
public static final String BUILDER_METRICS_EXTENDED_CAPTURE = "quarkus.builder.metrics.extended-capture";
34+
2935
static final Logger LOG = Logger.getLogger(BuildMetrics.class.getName());
3036

3137
private volatile LocalDateTime started;
3238
private volatile long duration;
3339
private final String buildTargetName;
34-
private final ConcurrentMap<String, BuildStepRecord> records = new ConcurrentHashMap<>();
35-
private final ConcurrentMap<String, Integer> buildItems = new ConcurrentHashMap<>();
40+
// build step id -> record
41+
private final ConcurrentMap<String, BuildStepRecord> records;
42+
// build item class -> count
43+
private final ConcurrentMap<String, Long> buildItems;
44+
// build step id -> produced build items
45+
private final ConcurrentMap<String, List<String>> buildItemsExtended;
3646
private final AtomicInteger idGenerator;
3747

3848
public BuildMetrics(String buildTargetName) {
49+
boolean enabled = Boolean.getBoolean(BUILDER_METRICS_ENABLED)
50+
// This system property is deprecated and will be removed
51+
|| Boolean.getBoolean("quarkus.debug.dump-build-metrics");
3952
this.buildTargetName = buildTargetName;
40-
this.idGenerator = new AtomicInteger();
53+
if (enabled) {
54+
this.idGenerator = new AtomicInteger();
55+
this.records = new ConcurrentHashMap<>();
56+
if (Boolean.getBoolean(BUILDER_METRICS_EXTENDED_CAPTURE)) {
57+
this.buildItemsExtended = new ConcurrentHashMap<>();
58+
this.buildItems = null;
59+
} else {
60+
this.buildItemsExtended = null;
61+
this.buildItems = new ConcurrentHashMap<>();
62+
}
63+
} else {
64+
this.idGenerator = null;
65+
this.records = null;
66+
this.buildItemsExtended = null;
67+
this.buildItems = null;
68+
}
4169
}
4270

4371
public Collection<BuildStepRecord> getRecords() {
@@ -53,82 +81,127 @@ public void buildFinished(long duration) {
5381
}
5482

5583
public void buildStepFinished(StepInfo stepInfo, String thread, LocalTime started, long duration) {
56-
records.put(stepInfo.getBuildStep().getId(),
57-
new BuildStepRecord(idGenerator.incrementAndGet(), stepInfo, thread, started, duration));
84+
if (enabled()) {
85+
records.put(stepInfo.getBuildStep().getId(),
86+
new BuildStepRecord(idGenerator.incrementAndGet(), stepInfo, thread, started, duration));
87+
}
5888
}
5989

60-
public void buildItemProduced(BuildItem buildItem) {
61-
buildItems.compute(buildItem.getClass().getName(), this::itemProduced);
90+
public void buildItemProduced(StepInfo stepInfo, BuildItem buildItem) {
91+
if (enabled()) {
92+
if (buildItems != null) {
93+
buildItems.compute(buildItem.getClass().getName(), this::itemProduced);
94+
} else {
95+
buildItemsExtended.compute(stepInfo.getBuildStep().getId(), (key, list) -> {
96+
String buildItemClass = buildItem.getClass().getName();
97+
if (list == null) {
98+
list = new ArrayList<>();
99+
}
100+
list.add(buildItemClass);
101+
return list;
102+
});
103+
}
104+
}
62105
}
63106

64-
private Integer itemProduced(String key, Integer val) {
65-
if (val == null) {
66-
return 1;
67-
}
68-
return val + 1;
107+
private Long itemProduced(String key, Long val) {
108+
return val == null ? 1 : val + 1;
69109
}
70110

71111
public void dumpTo(Path file) throws IOException {
72-
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss.SSS");
112+
if (enabled()) {
113+
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss.SSS");
114+
115+
List<BuildStepRecord> sortedSteps = new ArrayList<>(records.values());
116+
sortedSteps.sort(new Comparator<BuildStepRecord>() {
117+
@Override
118+
public int compare(BuildStepRecord o1, BuildStepRecord o2) {
119+
return Long.compare(o2.duration, o1.duration);
120+
}
121+
});
122+
123+
JsonObjectBuilder json = Json.object();
124+
json.put("buildTarget", buildTargetName);
125+
json.put("started", started.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
126+
json.put("duration", duration);
127+
128+
JsonArrayBuilder steps = Json.array();
129+
json.put("records", steps);
130+
for (BuildStepRecord rec : sortedSteps) {
131+
JsonObjectBuilder recObject = Json.object();
132+
recObject.put("id", rec.id);
133+
recObject.put("stepId", rec.stepInfo.getBuildStep().getId());
134+
recObject.put("thread", rec.thread);
135+
recObject.put("started", rec.started.format(formatter));
136+
recObject.put("duration", rec.duration);
137+
JsonArrayBuilder dependentsArray = Json.array();
138+
for (StepInfo dependent : rec.stepInfo.getDependents()) {
139+
BuildStepRecord dependentRecord = records.get(dependent.getBuildStep().getId());
140+
if (dependentRecord != null) {
141+
dependentsArray.add(dependentRecord.id);
142+
} else {
143+
LOG.warnf("Dependent record not found for stepId: %s", dependent.getBuildStep().getId());
144+
}
145+
}
146+
recObject.put("dependents", dependentsArray);
147+
if (buildItemsExtended != null) {
148+
JsonArrayBuilder producedItems = Json.array();
149+
List<String> items = buildItemsExtended.get(rec.stepInfo.getBuildStep().getId());
150+
if (items != null) {
151+
// build item class -> count
152+
Map<String, Long> counts = items
153+
.stream()
154+
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
155+
List<Entry<String, Long>> sortedItems = new ArrayList<>(counts.entrySet());
156+
sortedItems.sort(this::compareBuildItems);
157+
for (Entry<String, Long> e : sortedItems) {
158+
producedItems.add(Json.object()
159+
.put("item", e.getKey())
160+
.put("count", e.getValue()));
161+
}
162+
recObject.put("producedItems", producedItems);
163+
}
164+
}
165+
steps.add(recObject);
166+
}
73167

74-
List<BuildStepRecord> sortedSteps = new ArrayList<>(records.values());
75-
sortedSteps.sort(new Comparator<BuildStepRecord>() {
76-
@Override
77-
public int compare(BuildStepRecord o1, BuildStepRecord o2) {
78-
return Long.compare(o2.duration, o1.duration);
168+
List<Entry<String, Long>> sortedItems;
169+
if (buildItemsExtended != null) {
170+
// build item class -> count
171+
Map<String, Long> counts = buildItemsExtended.values()
172+
.stream()
173+
.flatMap(List::stream)
174+
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
175+
sortedItems = new ArrayList<>(counts.entrySet());
176+
} else {
177+
sortedItems = new ArrayList<>(buildItems.size());
178+
buildItems.entrySet().forEach(sortedItems::add);
79179
}
80-
});
81-
82-
JsonObjectBuilder json = Json.object();
83-
json.put("buildTarget", buildTargetName);
84-
json.put("started", started.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
85-
json.put("duration", duration);
86-
87-
JsonArrayBuilder steps = Json.array();
88-
json.put("records", steps);
89-
for (BuildStepRecord rec : sortedSteps) {
90-
JsonObjectBuilder recObject = Json.object();
91-
recObject.put("id", rec.id);
92-
recObject.put("stepId", rec.stepInfo.getBuildStep().getId());
93-
recObject.put("thread", rec.thread);
94-
recObject.put("started", rec.started.format(formatter));
95-
recObject.put("duration", rec.duration);
96-
JsonArrayBuilder dependentsArray = Json.array();
97-
for (StepInfo dependent : rec.stepInfo.getDependents()) {
98-
BuildStepRecord dependentRecord = records.get(dependent.getBuildStep().getId());
99-
if (dependentRecord != null) {
100-
dependentsArray.add(dependentRecord.id);
101-
} else {
102-
LOG.warnf("Dependent record not found for stepId: %s", dependent.getBuildStep().getId());
103-
}
180+
sortedItems.sort(this::compareBuildItems);
181+
JsonArrayBuilder items = Json.array();
182+
json.put("items", items);
183+
long itemsCount = 0;
184+
for (Entry<String, Long> e : sortedItems) {
185+
JsonObjectBuilder itemObject = Json.object();
186+
itemObject.put("class", e.getKey());
187+
itemObject.put("count", e.getValue());
188+
items.add(itemObject);
189+
itemsCount += e.getValue();
104190
}
105-
recObject.put("dependents", dependentsArray);
106-
steps.add(recObject);
107-
}
191+
json.put("itemsCount", itemsCount);
108192

109-
List<Entry<String, Integer>> sortedItems = new ArrayList<>(buildItems.size());
110-
buildItems.entrySet().forEach(sortedItems::add);
111-
sortedItems.sort(new Comparator<Entry<String, Integer>>() {
112-
@Override
113-
public int compare(Entry<String, Integer> o1, Entry<String, Integer> o2) {
114-
return Integer.compare(o2.getValue(), o1.getValue());
193+
try (BufferedWriter writer = new BufferedWriter(new FileWriter(file.toFile(), StandardCharsets.UTF_8))) {
194+
json.appendTo(writer);
115195
}
116-
});
117-
JsonArrayBuilder items = Json.array();
118-
json.put("items", items);
119-
Integer itemsCount = 0;
120-
for (Entry<String, Integer> e : sortedItems) {
121-
JsonObjectBuilder itemObject = Json.object();
122-
itemObject.put("class", e.getKey());
123-
itemObject.put("count", e.getValue());
124-
items.add(itemObject);
125-
itemsCount += e.getValue();
126196
}
127-
json.put("itemsCount", itemsCount);
197+
}
128198

129-
try (BufferedWriter writer = new BufferedWriter(new FileWriter(file.toFile(), StandardCharsets.UTF_8))) {
130-
json.appendTo(writer);
131-
}
199+
private boolean enabled() {
200+
return records != null;
201+
}
202+
203+
private int compareBuildItems(Entry<String, Long> o1, Entry<String, Long> o2) {
204+
return Long.compare(o2.getValue(), o1.getValue());
132205
}
133206

134207
public static class BuildStepRecord {

core/deployment/src/main/java/io/quarkus/deployment/DebugConfig.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,10 @@ public interface DebugConfig {
4444

4545
/**
4646
* If set to true then dump the build metrics to a JSON file in the build directory.
47+
*
48+
* @deprecated Use {@link io.quarkus.runtime.BuilderConfig#Metrics()} instead.
4749
*/
50+
@Deprecated(forRemoval = true, since = "3.31")
4851
@WithDefault("false")
4952
boolean dumpBuildMetrics();
5053
}

core/deployment/src/main/java/io/quarkus/deployment/QuarkusAugmentor.java

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import io.quarkus.builder.BuildChain;
2222
import io.quarkus.builder.BuildChainBuilder;
2323
import io.quarkus.builder.BuildExecutionBuilder;
24+
import io.quarkus.builder.BuildMetrics;
2425
import io.quarkus.builder.BuildResult;
2526
import io.quarkus.builder.item.BuildItem;
2627
import io.quarkus.deployment.builditem.AdditionalApplicationArchiveBuildItem;
@@ -132,6 +133,14 @@ public BuildResult run() throws Exception {
132133
if (launchMode.isDevOrTest()) {
133134
chainBuilder.addFinal(RuntimeApplicationShutdownBuildItem.class);
134135
}
136+
if (System.getProperty(BuildMetrics.BUILDER_METRICS_ENABLED) == null
137+
&& launchMode.isDev()
138+
&& !launchMode.isRemoteDev()) {
139+
// If quarkus.builder.metrics.enabled is not set then
140+
// collect build metrics in dev mode but not in remote-dev
141+
// (as it could cause issues with container permissions)
142+
System.setProperty("quarkus.builder.metrics.enabled", "true");
143+
}
135144

136145
final ArchiveRootBuildItem.Builder rootBuilder = ArchiveRootBuildItem.builder();
137146
if (root != null) {
@@ -162,18 +171,16 @@ public BuildResult run() throws Exception {
162171
+ "ms";
163172
if (launchMode.isProduction()) {
164173
log.info(message);
165-
if (Boolean.parseBoolean(System.getProperty("quarkus.debug.dump-build-metrics"))) {
166-
buildResult.getMetrics().dumpTo(targetDir.resolve("build-metrics.json"));
167-
}
168174
} else {
169175
//test and dev mode already report the total startup time, no need to add noise to the logs
170176
log.debug(message);
177+
}
171178

172-
// Dump the metrics in the dev mode but not remote-dev (as it could cause issues with container permissions)
173-
if (launchMode.isDev() && !launchMode.isRemoteDev()) {
174-
buildResult.getMetrics().dumpTo(targetDir.resolve("build-metrics.json"));
175-
}
179+
// If enabled then dump build metrics to a JSON file in the build directory
180+
if (targetDir != null) {
181+
buildResult.getMetrics().dumpTo(targetDir.resolve("build-metrics.json"));
176182
}
183+
177184
return buildResult;
178185
} finally {
179186
try {

core/runtime/src/main/java/io/quarkus/runtime/BuilderConfig.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import io.quarkus.runtime.annotations.ConfigPhase;
66
import io.quarkus.runtime.annotations.ConfigRoot;
77
import io.smallrye.config.ConfigMapping;
8+
import io.smallrye.config.WithDefault;
89

910
/**
1011
* Builder.
@@ -26,4 +27,26 @@ public interface BuilderConfig {
2627
* Whether to log the cause of a conflict.
2728
*/
2829
Optional<Boolean> logConflictCause();
30+
31+
/**
32+
* Build metrics configuration.
33+
*/
34+
Metrics Metrics();
35+
36+
interface Metrics {
37+
38+
/**
39+
* If set to true then dump the build metrics to a JSON file in the build directory.
40+
*/
41+
@WithDefault("false")
42+
boolean enabled();
43+
44+
/**
45+
* If set to true then the collection of metrics is enhanced but the size of the generated JSON file may grow
46+
* significantly.
47+
*/
48+
@WithDefault("false")
49+
boolean extendedCapture();
50+
51+
}
2952
}

0 commit comments

Comments
 (0)