Skip to content

Commit

Permalink
CLI: add an option to generate a simple build report
Browse files Browse the repository at this point in the history
- resolves #29174
  • Loading branch information
mkouba committed Aug 11, 2023
1 parent b4f8555 commit f774cc8
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 2 deletions.
4 changes: 4 additions & 0 deletions devtools/cli/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@
<groupId>io.quarkus</groupId>
<artifactId>quarkus-netty</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-qute</artifactId>
</dependency>
<!--
Somehow, we need this as otherwise you end up with:
java.lang.ClassNotFoundException: io.netty.handler.codec.http2.DefaultHttp2FrameWriter
Expand Down
11 changes: 10 additions & 1 deletion devtools/cli/src/main/java/io/quarkus/cli/Build.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,23 @@ public Integer call() {
output.throwIfUnmatchedArguments(spec.commandLine());

BuildSystemRunner runner = getRunner();
if (buildOptions.generateReport) {
params.add("-Dquarkus.debug.dump-build-metrics=true");
}
BuildSystemRunner.BuildCommandArgs commandArgs = runner.prepareBuild(buildOptions, runMode, params);

if (runMode.isDryRun()) {
dryRunBuild(spec.commandLine().getHelp(), runner.getBuildTool(), commandArgs);
return CommandLine.ExitCode.OK;
}

return runner.run(commandArgs);
int exitCode = runner.run(commandArgs);
if (exitCode == CommandLine.ExitCode.OK && buildOptions.generateReport) {
output.printText(new String[] {
"\nBuild report generated: " + new BuildReport(runner).generate().toPath().toAbsolutePath().toString()
});
}
return exitCode;
} catch (Exception e) {
return output.handleCommandException(e,
"Unable to build project: " + e.getMessage());
Expand Down
82 changes: 82 additions & 0 deletions devtools/cli/src/main/java/io/quarkus/cli/BuildReport.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package io.quarkus.cli;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import io.quarkus.cli.build.BuildSystemRunner;
import io.quarkus.qute.CheckedTemplate;
import io.quarkus.qute.TemplateInstance;

@CheckedTemplate(basePath = "")
public class BuildReport {

static native TemplateInstance buildReport(String buildTarget, long duration, Set<String> threads,
List<BuildStepRecord> records);

private final BuildSystemRunner runner;

public BuildReport(BuildSystemRunner runner) {
this.runner = runner;
}

File generate() throws IOException {
File metricsJsonFile = runner.getProjectRoot().resolve(runner.getBuildTool().getBuildDirectory())
.resolve("build-metrics.json").toFile();
if (!metricsJsonFile.canRead()) {
throw new IllegalStateException("Build metrics file cannot be read: " + metricsJsonFile);
}

ObjectMapper objectMapper = new ObjectMapper();
JsonNode root = objectMapper.readTree(metricsJsonFile);

String buildTarget = root.get("buildTarget").asText();
long duration = root.get("duration").asLong();
Set<String> threads = new HashSet<>();
List<BuildStepRecord> records = new ArrayList<>();

for (JsonNode record : root.get("records")) {
String thread = record.get("thread").asText();
threads.add(thread);
records.add(new BuildStepRecord(record.get("stepId").asText(), record.get("started").asText(),
record.get("duration").asLong(), thread));
}

File output = runner.getProjectRoot().resolve(runner.getBuildTool().getBuildDirectory())
.resolve("build-report.html").toFile();
if (output.exists() && !output.canWrite()) {
throw new IllegalStateException("Build report file cannot be written to: " + output);
}
try {
Files.writeString(output.toPath(),
BuildReport.buildReport(buildTarget, duration, threads, records).render());
return output;
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
}

public static class BuildStepRecord {

public final String stepId;
public final String started;
public final long duration;
public final String thread;

public BuildStepRecord(String stepId, String started, long duration, String thread) {
this.stepId = stepId;
this.started = started;
this.duration = duration;
this.thread = thread;
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,16 @@ public class BuildOptions {
"--no-tests" }, description = "Run tests.", negatable = true, defaultValue = "false")
public boolean skipTests = false;

@CommandLine.Option(order = 7, names = { "--report" }, description = "Generate build report.", defaultValue = "false")
public boolean generateReport = false;

public boolean skipTests() {
return skipTests;
}

@Override
public String toString() {
return "BuildOptions [buildNative=" + buildNative + ", clean=" + clean + ", offline=" + offline + ", skipTests="
+ skipTests + "]";
+ skipTests + ", generateReport=" + generateReport + "]";
}
}
64 changes: 64 additions & 0 deletions devtools/cli/src/main/resources/templates/buildReport.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
<!DOCTYPE html>
<html>

<head>
<meta charset="UTF-8">
<title>Quarkus Build Report - {buildTarget}</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/font/bootstrap-icons.css">
<style>
body {
font-family: sans;
width: max-content;
padding: 2rem;
}
</style>
</head>

<body>
<div class="container-fluid">

<h1>Quarkus Build Report - {buildTarget}</h1>

<p class="lead mt-4 mb-4">
Executed <strong>{records.size}</strong> build steps on <strong>{threads.size}</strong> threads in
<strong>{duration} ms</strong>.
</p>

<h2>Build Steps</h2>

<table class="table table-striped mb-4">
<thead class="thead-dark">
<tr>
<th scope="col">#</th>
<th scope="col">Build Step</th>
<th scope="col">Started</th>
<th scope="col">Duration</th>
<th scope="col">Thread</th>
</tr>
</thead>
<tbody>
{#for record in records}
<tr>
<td>{record_count}</td>
<td>
{record.stepId}
</td>
<td>
{record.started}
</td>
<td>
{#if record.duration < 1} &lt; 1ms {#else} {record.duration} ms {/if} </td>
<td>
{record.thread}
</td>
{/for}
</tbody>
</table>
</div>

<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>

</body>

</html>

0 comments on commit f774cc8

Please sign in to comment.