Skip to content

Commit e2b50ad

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 or in the dev mode - deprecate quarkus.debug.dump-build-metrics
1 parent 26c975f commit e2b50ad

File tree

9 files changed

+287
-86
lines changed

9 files changed

+287
-86
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: 107 additions & 67 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

@@ -28,16 +31,24 @@ public class BuildMetrics {
2831

2932
static final Logger LOG = Logger.getLogger(BuildMetrics.class.getName());
3033

34+
private final boolean enabled;
3135
private volatile LocalDateTime started;
3236
private volatile long duration;
3337
private final String buildTargetName;
34-
private final ConcurrentMap<String, BuildStepRecord> records = new ConcurrentHashMap<>();
35-
private final ConcurrentMap<String, Integer> buildItems = new ConcurrentHashMap<>();
38+
// build step id -> record
39+
private final ConcurrentMap<String, BuildStepRecord> records;
40+
// build step id -> produced build items
41+
private final ConcurrentMap<String, List<String>> buildItems;
3642
private final AtomicInteger idGenerator;
3743

3844
public BuildMetrics(String buildTargetName) {
45+
this.enabled = Boolean.getBoolean("quarkus.builder.metrics.enabled")
46+
// This system property is deprecated and will be removed
47+
|| Boolean.getBoolean("quarkus.debug.dump-build-metrics");
3948
this.buildTargetName = buildTargetName;
40-
this.idGenerator = new AtomicInteger();
49+
this.idGenerator = enabled ? new AtomicInteger() : null;
50+
this.records = enabled ? new ConcurrentHashMap<>() : null;
51+
this.buildItems = enabled ? new ConcurrentHashMap<>() : null;
4152
}
4253

4354
public Collection<BuildStepRecord> getRecords() {
@@ -53,84 +64,113 @@ public void buildFinished(long duration) {
5364
}
5465

5566
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));
58-
}
59-
60-
public void buildItemProduced(BuildItem buildItem) {
61-
buildItems.compute(buildItem.getClass().getName(), this::itemProduced);
67+
if (enabled) {
68+
records.put(stepInfo.getBuildStep().getId(),
69+
new BuildStepRecord(idGenerator.incrementAndGet(), stepInfo, thread, started, duration));
70+
}
6271
}
6372

64-
private Integer itemProduced(String key, Integer val) {
65-
if (val == null) {
66-
return 1;
73+
public void buildItemProduced(StepInfo stepInfo, BuildItem buildItem) {
74+
if (enabled) {
75+
buildItems.compute(stepInfo.getBuildStep().getId(), (key, list) -> {
76+
String buildItemClass = buildItem.getClass().getName();
77+
if (list == null) {
78+
List<String> newList = new ArrayList<>();
79+
newList.add(buildItemClass);
80+
return newList;
81+
} else {
82+
list.add(buildItemClass);
83+
return list;
84+
}
85+
});
6786
}
68-
return val + 1;
6987
}
7088

7189
public void dumpTo(Path file) throws IOException {
72-
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss.SSS");
73-
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);
79-
}
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());
90+
if (enabled) {
91+
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss.SSS");
92+
boolean extendedCapture = Boolean.getBoolean("quarkus.builder.metrics.extended-capture");
93+
94+
List<BuildStepRecord> sortedSteps = new ArrayList<>(records.values());
95+
sortedSteps.sort(new Comparator<BuildStepRecord>() {
96+
@Override
97+
public int compare(BuildStepRecord o1, BuildStepRecord o2) {
98+
return Long.compare(o2.duration, o1.duration);
10399
}
100+
});
101+
102+
JsonObjectBuilder json = Json.object();
103+
json.put("buildTarget", buildTargetName);
104+
json.put("started", started.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
105+
json.put("duration", duration);
106+
107+
JsonArrayBuilder steps = Json.array();
108+
json.put("records", steps);
109+
for (BuildStepRecord rec : sortedSteps) {
110+
JsonObjectBuilder recObject = Json.object();
111+
recObject.put("id", rec.id);
112+
recObject.put("stepId", rec.stepInfo.getBuildStep().getId());
113+
recObject.put("thread", rec.thread);
114+
recObject.put("started", rec.started.format(formatter));
115+
recObject.put("duration", rec.duration);
116+
JsonArrayBuilder dependentsArray = Json.array();
117+
for (StepInfo dependent : rec.stepInfo.getDependents()) {
118+
BuildStepRecord dependentRecord = records.get(dependent.getBuildStep().getId());
119+
if (dependentRecord != null) {
120+
dependentsArray.add(dependentRecord.id);
121+
} else {
122+
LOG.warnf("Dependent record not found for stepId: %s", dependent.getBuildStep().getId());
123+
}
124+
}
125+
recObject.put("dependents", dependentsArray);
126+
if (extendedCapture) {
127+
JsonArrayBuilder producedItems = Json.array();
128+
List<String> items = buildItems.get(rec.stepInfo.getBuildStep().getId());
129+
if (items != null) {
130+
Map<String, Long> counts = items
131+
.stream()
132+
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
133+
List<Entry<String, Long>> sortedItems = new ArrayList<>(counts.entrySet());
134+
sortedItems.sort(this::compareBuildItems);
135+
for (Entry<String, Long> e : sortedItems) {
136+
producedItems.add(Json.object()
137+
.put("item", e.getKey())
138+
.put("count", e.getValue()));
139+
}
140+
recObject.put("producedItems", producedItems);
141+
}
142+
}
143+
steps.add(recObject);
104144
}
105-
recObject.put("dependents", dependentsArray);
106-
steps.add(recObject);
107-
}
108145

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());
146+
Map<String, Long> counts = buildItems.values()
147+
.stream()
148+
.flatMap(List::stream)
149+
.collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
150+
List<Entry<String, Long>> sortedItems = new ArrayList<>(counts.entrySet());
151+
sortedItems.sort(this::compareBuildItems);
152+
JsonArrayBuilder items = Json.array();
153+
json.put("items", items);
154+
long itemsCount = 0;
155+
for (Entry<String, Long> e : sortedItems) {
156+
JsonObjectBuilder itemObject = Json.object();
157+
itemObject.put("class", e.getKey());
158+
itemObject.put("count", e.getValue());
159+
items.add(itemObject);
160+
itemsCount += e.getValue();
115161
}
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();
126-
}
127-
json.put("itemsCount", itemsCount);
162+
json.put("itemsCount", itemsCount);
128163

129-
try (BufferedWriter writer = new BufferedWriter(new FileWriter(file.toFile(), StandardCharsets.UTF_8))) {
130-
json.appendTo(writer);
164+
try (BufferedWriter writer = new BufferedWriter(new FileWriter(file.toFile(), StandardCharsets.UTF_8))) {
165+
json.appendTo(writer);
166+
}
131167
}
132168
}
133169

170+
private int compareBuildItems(Entry<String, Long> o1, Entry<String, Long> o2) {
171+
return Long.compare(o2.getValue(), o1.getValue());
172+
}
173+
134174
public static class BuildStepRecord {
135175

136176
/**

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: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,10 @@ public BuildResult run() throws Exception {
132132
if (launchMode.isDevOrTest()) {
133133
chainBuilder.addFinal(RuntimeApplicationShutdownBuildItem.class);
134134
}
135+
// Always collect build metrics in dev mode but not in remote-dev (as it could cause issues with container permissions)
136+
if (launchMode.isDev() && !launchMode.isRemoteDev()) {
137+
System.setProperty("quarkus.builder.metrics.enabled", "true");
138+
}
135139

136140
final ArchiveRootBuildItem.Builder rootBuilder = ArchiveRootBuildItem.builder();
137141
if (root != null) {
@@ -162,18 +166,16 @@ public BuildResult run() throws Exception {
162166
+ "ms";
163167
if (launchMode.isProduction()) {
164168
log.info(message);
165-
if (Boolean.parseBoolean(System.getProperty("quarkus.debug.dump-build-metrics"))) {
166-
buildResult.getMetrics().dumpTo(targetDir.resolve("build-metrics.json"));
167-
}
168169
} else {
169170
//test and dev mode already report the total startup time, no need to add noise to the logs
170171
log.debug(message);
172+
}
171173

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-
}
174+
// If enabled then dump build metrics to a JSON file in the build directory
175+
if (targetDir != null) {
176+
buildResult.getMetrics().dumpTo(targetDir.resolve("build-metrics.json"));
176177
}
178+
177179
return buildResult;
178180
} finally {
179181
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)