Right-pad projectIds to improve mojo readability in the threaded view #288

This commit is contained in:
Peter Palaga
2020-12-25 21:04:16 +01:00
parent 519424deba
commit c6de4dbd9a
8 changed files with 231 additions and 92 deletions

View File

@@ -231,7 +231,6 @@ public class DefaultClient implements Client {
try (DaemonClientConnection daemon = connector.connect(output)) {
output.setDaemonDispatch(daemon::dispatch);
output.setDaemonReceive(daemon::enqueue);
output.accept(Message.buildStatus("Connected to daemon"));
daemon.dispatch(new Message.BuildRequest(
args,
@@ -239,7 +238,7 @@ public class DefaultClient implements Client {
parameters.multiModuleProjectDirectory().toString(),
System.getenv()));
output.accept(Message.buildStatus("Build request sent"));
output.accept(Message.buildStatus("Daemon started, scanning for projects..."));
// We've sent the request, so it gives us a bit of time to purge the logs
AtomicReference<String> purgeMessage = new AtomicReference<>();

View File

@@ -32,7 +32,9 @@ public abstract class Message {
public static final int BUILD_REQUEST = 0;
public static final int BUILD_STARTED = 1;
public static final int BUILD_FINISHED = 2;
/** A {@link StringMessage} bearing the {@code artifactId} of the project whose build just started */
public static final int PROJECT_STARTED = 3;
/** A {@link StringMessage} bearing the {@code artifactId} of the project whose build just finished */
public static final int PROJECT_STOPPED = 4;
public static final int MOJO_STARTED = 5;
public static final int PROJECT_LOG_MESSAGE = 6;
@@ -69,9 +71,8 @@ public abstract class Message {
return BuildStarted.read(input);
case BUILD_FINISHED:
return BuildFinished.read(input);
case PROJECT_STARTED:
case PROJECT_STOPPED:
case MOJO_STARTED:
return MojoStartedEvent.read(input);
case PROJECT_LOG_MESSAGE:
case DISPLAY:
return ProjectEvent.read(type, input);
@@ -85,6 +86,8 @@ public abstract class Message {
return Prompt.read(input);
case PROMPT_RESPONSE:
return PromptResponse.read(input);
case PROJECT_STARTED:
case PROJECT_STOPPED:
case BUILD_STATUS:
case BUILD_LOG_MESSAGE:
return StringMessage.read(type, input);
@@ -420,12 +423,6 @@ public abstract class Message {
private String mnemonic() {
switch (type) {
case PROJECT_STARTED:
return "ProjectStarted";
case PROJECT_STOPPED:
return "ProjectStopped";
case MOJO_STARTED:
return "MojoStarted";
case PROJECT_LOG_MESSAGE:
return "ProjectLogMessage";
default:
@@ -441,24 +438,104 @@ public abstract class Message {
}
}
public static class MojoStartedEvent extends Message {
final String artifactId;
final String pluginGroupId;
final String pluginArtifactId;
final String pluginVersion;
final String mojo;
final String executionId;
public static MojoStartedEvent read(DataInputStream input) throws IOException {
final String artifactId = readUTF(input);
final String pluginGroupId = readUTF(input);
final String pluginArtifactId = readUTF(input);
final String pluginVersion = readUTF(input);
final String mojo = readUTF(input);
final String executionId = readUTF(input);
return new MojoStartedEvent(artifactId, pluginGroupId, pluginArtifactId, pluginVersion, mojo, executionId);
}
public MojoStartedEvent(String artifactId, String pluginGroupId, String pluginArtifactId,
String pluginVersion, String mojo, String executionId) {
super(Message.MOJO_STARTED);
this.artifactId = Objects.requireNonNull(artifactId, "artifactId cannot be null");
this.pluginGroupId = Objects.requireNonNull(pluginGroupId, "pluginGroupId cannot be null");
this.pluginArtifactId = Objects.requireNonNull(pluginArtifactId, "pluginArtifactId cannot be null");
this.pluginVersion = Objects.requireNonNull(pluginVersion, "pluginVersion cannot be null");
this.mojo = Objects.requireNonNull(mojo, "mojo cannot be null");
this.executionId = Objects.requireNonNull(executionId, "executionId cannot be null");
}
public String getArtifactId() {
return artifactId;
}
public String getPluginGroupId() {
return pluginGroupId;
}
public String getPluginArtifactId() {
return pluginArtifactId;
}
public String getPluginVersion() {
return pluginVersion;
}
public String getExecutionId() {
return executionId;
}
public String getMojo() {
return mojo;
}
@Override
public String toString() {
return "MojoStarted{" +
"artifactId='" + artifactId + '\'' +
", pluginGroupId='" + pluginGroupId + '\'' +
", pluginArtifactId='" + pluginArtifactId + '\'' +
", pluginVersion='" + pluginVersion + '\'' +
", mojo='" + mojo + '\'' +
", executionId='" + executionId + '\'' +
'}';
}
@Override
public void write(DataOutputStream output) throws IOException {
super.write(output);
writeUTF(output, artifactId);
writeUTF(output, pluginGroupId);
writeUTF(output, pluginArtifactId);
writeUTF(output, pluginVersion);
writeUTF(output, mojo);
writeUTF(output, executionId);
}
}
public static class BuildStarted extends Message {
final String projectId;
final int projectCount;
final int maxThreads;
final int artifactIdDisplayLength;
public static BuildStarted read(DataInputStream input) throws IOException {
final String projectId = readUTF(input);
final int projectCount = input.readInt();
final int maxThreads = input.readInt();
return new BuildStarted(projectId, projectCount, maxThreads);
final int artifactIdDisplayLength = input.readInt();
return new BuildStarted(projectId, projectCount, maxThreads, artifactIdDisplayLength);
}
public BuildStarted(String projectId, int projectCount, int maxThreads) {
public BuildStarted(String projectId, int projectCount, int maxThreads, int artifactIdDisplayLength) {
super(BUILD_STARTED);
this.projectId = projectId;
this.projectCount = projectCount;
this.maxThreads = maxThreads;
this.artifactIdDisplayLength = artifactIdDisplayLength;
}
public String getProjectId() {
@@ -473,11 +550,15 @@ public abstract class Message {
return maxThreads;
}
public int getArtifactIdDisplayLength() {
return artifactIdDisplayLength;
}
@Override
public String toString() {
return "BuildStarted{" +
"projectId='" + projectId + "', projectCount=" + projectCount +
", maxThreads='" + maxThreads + "'}";
", maxThreads=" + maxThreads + ", artifactIdDisplayLength=" + artifactIdDisplayLength + "}";
}
@Override
@@ -486,6 +567,7 @@ public abstract class Message {
writeUTF(output, projectId);
output.writeInt(projectCount);
output.writeInt(maxThreads);
output.writeInt(artifactIdDisplayLength);
}
}
@@ -545,6 +627,10 @@ public abstract class Message {
private String mnemonic() {
switch (type) {
case PROJECT_STARTED:
return "ProjectStarted";
case PROJECT_STOPPED:
return "ProjectStopped";
case BUILD_STATUS:
return "BuildStatus";
case KEYBOARD_INPUT:
@@ -698,16 +784,18 @@ public abstract class Message {
return new StringMessage(KEYBOARD_INPUT, String.valueOf(keyStroke));
}
public static ProjectEvent projectStarted(String projectId, String display) {
return new ProjectEvent(Message.PROJECT_STARTED, projectId, display);
public static StringMessage projectStarted(String projectId) {
return new StringMessage(Message.PROJECT_STARTED, projectId);
}
public static ProjectEvent projectStopped(String projectId, String display) {
return new ProjectEvent(PROJECT_STOPPED, projectId, display);
public static StringMessage projectStopped(String projectId) {
return new StringMessage(PROJECT_STOPPED, projectId);
}
public static Message mojoStarted(String projectId, String display) {
return new ProjectEvent(Message.MOJO_STARTED, projectId, display);
public static Message mojoStarted(String artifactId, String pluginGroupId, String pluginArtifactId,
String pluginVersion, String mojo, String executionId) {
return new MojoStartedEvent(artifactId, pluginGroupId, pluginArtifactId, pluginVersion, mojo, executionId);
}
public static ProjectEvent display(String projectId, String message) {

View File

@@ -44,6 +44,7 @@ import org.jline.utils.Display;
import org.mvndaemon.mvnd.common.Message;
import org.mvndaemon.mvnd.common.Message.BuildException;
import org.mvndaemon.mvnd.common.Message.BuildStarted;
import org.mvndaemon.mvnd.common.Message.MojoStartedEvent;
import org.mvndaemon.mvnd.common.Message.ProjectEvent;
import org.mvndaemon.mvnd.common.Message.StringMessage;
@@ -55,6 +56,8 @@ public class TerminalOutput implements ClientOutput {
public static final int CTRL_B = 'B' & 0x1f;
public static final int CTRL_L = 'L' & 0x1f;
public static final int CTRL_M = 'M' & 0x1f;
private static final AttributedStyle GREEN_FOREGROUND = new AttributedStyle().foreground(AttributedStyle.GREEN);
private static final AttributedStyle CYAN_FOREGROUND = new AttributedStyle().foreground(AttributedStyle.CYAN);
private final Terminal terminal;
private final Terminal.SignalHandler previousIntHandler;
@@ -85,6 +88,7 @@ public class TerminalOutput implements ClientOutput {
/** String format for formatting the number of projects done with padding based on {@link #totalProjects} */
private String projectsDoneFomat;
private int maxThreads;
private String artifactIdFormat;
/** String format for formatting the actual/hidden/max thread counts */
private String threadsFormat;
private int linesPerProject = 0;
@@ -99,7 +103,7 @@ public class TerminalOutput implements ClientOutput {
*/
static class Project {
final String id;
String status;
MojoStartedEvent runningExecution;
final List<String> log = new ArrayList<>();
public Project(String id) {
@@ -171,6 +175,7 @@ public class TerminalOutput implements ClientOutput {
final int totalProjectsDigits = (int) (Math.log10(totalProjects) + 1);
this.projectsDoneFomat = "%" + totalProjectsDigits + "d";
this.maxThreads = bs.getMaxThreads();
this.artifactIdFormat = "%-" + bs.getArtifactIdDisplayLength() + "s ";
final int maxThreadsDigits = (int) (Math.log10(maxThreads) + 1);
this.threadsFormat = "%" + (maxThreadsDigits * 3 + 2) + "s";
if (maxThreads <= 1 || totalProjects <= 1) {
@@ -213,16 +218,22 @@ public class TerminalOutput implements ClientOutput {
terminal.flush();
return false;
}
case Message.PROJECT_STARTED:
case Message.PROJECT_STARTED: {
StringMessage be = (StringMessage) entry;
final String artifactId = be.getMessage();
projects.put(artifactId, new Project(artifactId));
break;
}
case Message.MOJO_STARTED: {
ProjectEvent be = (ProjectEvent) entry;
Project prj = projects.computeIfAbsent(be.getProjectId(), Project::new);
prj.status = be.getMessage();
final MojoStartedEvent execution = (MojoStartedEvent) entry;
final Project prj = projects.get(execution.getArtifactId());
prj.runningExecution = execution;
break;
}
case Message.PROJECT_STOPPED: {
ProjectEvent be = (ProjectEvent) entry;
Project prj = projects.remove(be.getProjectId());
StringMessage be = (StringMessage) entry;
final String artifactId = be.getMessage();
Project prj = projects.remove(artifactId);
if (prj != null) {
prj.log.forEach(log);
}
@@ -444,7 +455,8 @@ public class TerminalOutput implements ClientOutput {
}
final List<AttributedString> lines = new ArrayList<>(rows);
int dispLines = rows - 1; // for the build status line
dispLines--; // there's a bug which sometimes make the cursor goes one line below, so keep one more line empty at the end
dispLines--; // there's a bug which sometimes make the cursor goes one line below, so keep one more line empty
// at the end
final int projectsCount = projects.size();
addStatusLine(lines, dispLines, projectsCount);
@@ -481,7 +493,6 @@ public class TerminalOutput implements ClientOutput {
private void addStatusLine(final List<AttributedString> lines, int dispLines, final int projectsCount) {
if (name != null || buildStatus != null) {
AttributedStringBuilder asb = new AttributedStringBuilder();
StringBuilder statusLine = new StringBuilder(64);
if (name != null) {
asb.append("Building ");
asb.style(AttributedStyle.BOLD);
@@ -489,8 +500,9 @@ public class TerminalOutput implements ClientOutput {
asb.style(AttributedStyle.DEFAULT);
/* Threads */
statusLine
asb
.append(" threads used/hidden/max: ")
.style(AttributedStyle.BOLD)
.append(
String.format(
threadsFormat,
@@ -499,50 +511,63 @@ public class TerminalOutput implements ClientOutput {
.append('/')
.append(Math.max(0, projectsCount - dispLines))
.append('/')
.append(maxThreads).toString()));
.append(maxThreads).toString()))
.style(AttributedStyle.DEFAULT);
/* Progress */
statusLine
asb
.append(" progress: ")
.style(AttributedStyle.BOLD)
.append(String.format(projectsDoneFomat, doneProjects))
.append('/')
.append(totalProjects)
.append(String.valueOf(totalProjects))
.append(' ')
.append(String.format("%3d", doneProjects * 100 / totalProjects))
.append('%');
.append('%')
.style(AttributedStyle.DEFAULT);
} else if (buildStatus != null) {
statusLine.append(buildStatus);
asb
.style(AttributedStyle.BOLD)
.append(buildStatus)
.style(AttributedStyle.DEFAULT);
}
/* Time */
statusLine.append(" time: ");
long sec = (System.currentTimeMillis() - this.start) / 1000;
statusLine.append(String.format("%02d:%02d", sec / 60, sec % 60));
asb
.append(" time: ")
.style(AttributedStyle.BOLD)
.append(String.format("%02d:%02d", sec / 60, sec % 60))
.style(AttributedStyle.DEFAULT);
asb.append(statusLine.toString());
lines.add(asb.toAttributedString());
}
}
private void addProjectLine(final List<AttributedString> lines, Project prj) {
String str = prj.status != null ? prj.status : ":" + prj.id + ":<unknown>";
if (str.length() >= 1 && str.charAt(0) == ':') {
int ce = str.indexOf(':', 1);
final AttributedStringBuilder asb = new AttributedStringBuilder();
asb.append(":");
asb.style(AttributedStyle.BOLD);
if (ce > 0) {
asb.append(str, 1, ce);
asb.style(AttributedStyle.DEFAULT);
asb.append(str, ce, str.length());
} else {
asb.append(str, 1, str.length());
}
lines.add(asb.toAttributedString());
final MojoStartedEvent execution = prj.runningExecution;
final AttributedStringBuilder asb = new AttributedStringBuilder();
if (execution == null) {
asb
.append(':')
.append(prj.id);
} else {
lines.add(AttributedString.fromAnsi(str));
asb
.append(':')
.append(String.format(artifactIdFormat, prj.id))
.style(GREEN_FOREGROUND);
asb
.append(execution.getPluginArtifactId())
.append(':')
.append(execution.getMojo())
.style(AttributedStyle.DEFAULT)
.append(" @ ")
.style(CYAN_FOREGROUND)
.append(execution.getExecutionId())
.style(AttributedStyle.DEFAULT);
}
lines.add(asb.toAttributedString());
}
private static <T> List<T> lastN(List<T> list, int n) {

View File

@@ -16,8 +16,13 @@
package org.mvndaemon.mvnd.daemon;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import org.apache.maven.execution.ExecutionEvent;
import org.apache.maven.execution.MavenSession;
import org.apache.maven.plugin.MojoExecution;
import org.apache.maven.project.MavenProject;
import org.mvndaemon.mvnd.builder.DependencyGraph;
import org.mvndaemon.mvnd.common.Message;
@@ -42,11 +47,34 @@ public class ClientDispatcher extends BuildEventListener {
session.getRequest().getData().put(DependencyGraph.class.getName(), dependencyGraph);
final int maxThreads = degreeOfConcurrency == 1 ? 1 : dependencyGraph.computeMaxWidth(degreeOfConcurrency, 1000);
queue.add(new BuildStarted(getCurrentProject(session).getName(), session.getProjects().size(), maxThreads));
final List<MavenProject> projects = session.getProjects();
final int _90thArtifactIdLengthPercentile = artifactIdLength90thPercentile(projects);
queue.add(new BuildStarted(getCurrentProject(session).getArtifactId(), projects.size(), maxThreads,
_90thArtifactIdLengthPercentile));
}
static int artifactIdLength90thPercentile(List<MavenProject> projects) {
if (projects.size() == 1) {
return projects.get(0).getArtifactId().length();
}
Map<Integer, Integer> frequencyDistribution = new TreeMap<>();
for (MavenProject p : projects) {
frequencyDistribution.compute(p.getArtifactId().length(),
(k, v) -> (v == null) ? Integer.valueOf(1) : Integer.valueOf(v.intValue() + 1));
}
int _90PercCount = Math.round(0.9f * projects.size());
int cnt = 0;
for (Entry<Integer, Integer> en : frequencyDistribution.entrySet()) {
cnt += en.getValue().intValue();
if (cnt >= _90PercCount) {
return en.getKey().intValue();
}
}
throw new IllegalStateException("Could not compute the 90th percentile of the projects length from " + projects);
}
public void projectStarted(ExecutionEvent event) {
queue.add(Message.projectStarted(getProjectId(event), getProjectDisplay(event)));
queue.add(Message.projectStarted(event.getProject().getArtifactId()));
}
public void projectLogMessage(String projectId, String event) {
@@ -55,11 +83,18 @@ public class ClientDispatcher extends BuildEventListener {
}
public void projectFinished(ExecutionEvent event) {
queue.add(Message.projectStopped(getProjectId(event), getProjectDisplay(event)));
queue.add(Message.projectStopped(event.getProject().getArtifactId()));
}
public void mojoStarted(ExecutionEvent event) {
queue.add(Message.mojoStarted(getProjectId(event), getProjectDisplay(event)));
final MojoExecution execution = event.getMojoExecution();
queue.add(Message.mojoStarted(
event.getProject().getArtifactId(),
execution.getGroupId(),
execution.getArtifactId(),
execution.getVersion(),
execution.getGoal(),
execution.getExecutionId()));
}
public void finish(int exitCode) throws Exception {
@@ -89,15 +124,4 @@ public class ClientDispatcher extends BuildEventListener {
.orElse(mavenSession.getCurrentProject());
}
private String getProjectId(ExecutionEvent event) {
return event.getProject().getArtifactId();
}
private String getProjectDisplay(ExecutionEvent event) {
String projectId = getProjectId(event);
return event.getMojoExecution() != null
? ":" + projectId + ":" + event.getMojoExecution().toString()
: ":" + projectId;
}
}

View File

@@ -31,6 +31,7 @@ import org.mvndaemon.mvnd.client.Client;
import org.mvndaemon.mvnd.client.DaemonParameters;
import org.mvndaemon.mvnd.common.Message;
import org.mvndaemon.mvnd.common.Message.ProjectEvent;
import org.mvndaemon.mvnd.common.Message.StringMessage;
import org.mvndaemon.mvnd.junit.MvndTest;
import org.mvndaemon.mvnd.junit.TestUtils;
@@ -97,7 +98,7 @@ public class MultiModuleTest {
{
final List<String> filteredMessages = output.getMessages().stream()
.filter(m -> m.getType() == Message.PROJECT_STARTED)
.map(m -> ((ProjectEvent) m).getProjectId())
.map(m -> ((StringMessage) m).getMessage())
.collect(Collectors.toList());
Assertions.assertThat(filteredMessages)

View File

@@ -29,10 +29,6 @@ public class MvndTestUtil {
private MvndTestUtil() {
}
public static String plugin(Properties props, String artifactId) {
return artifactId + ":" + props.getProperty(artifactId + ".version");
}
public static Properties properties(Path pomXmlPath) {
try (Reader runtimeReader = Files.newBufferedReader(pomXmlPath, StandardCharsets.UTF_8)) {
final MavenXpp3Reader rxppReader = new MavenXpp3Reader();

View File

@@ -55,7 +55,7 @@ public class SingleModuleNativeIT {
Assertions.assertThat(installedJar).doesNotExist();
final TestClientOutput o = new TestClientOutput();
client.execute(o, "clean", "install", "-e").assertSuccess();
client.execute(o, "clean", "install", "-e", "-B").assertSuccess();
final Properties props = MvndTestUtil.properties(parameters.multiModuleProjectDirectory().resolve("pom.xml"));
final List<String> messages = o.getMessages().stream()
@@ -65,11 +65,13 @@ public class SingleModuleNativeIT {
Assertions.assertThat(messages)
.is(new MatchInOrderAmongOthers<>(
"Building single-module",
MvndTestUtil.plugin(props, "maven-clean-plugin") + ":clean",
MvndTestUtil.plugin(props, "maven-compiler-plugin") + ":compile",
MvndTestUtil.plugin(props, "maven-compiler-plugin") + ":testCompile",
MvndTestUtil.plugin(props, "maven-surefire-plugin") + ":test",
MvndTestUtil.plugin(props, "maven-install-plugin") + ":install",
mojoStartedLogMessage(props, "maven-clean-plugin", "clean", "default-clean"),
mojoStartedLogMessage(props, "maven-resources-plugin", "resources", "default-resources"),
mojoStartedLogMessage(props, "maven-compiler-plugin", "compile", "default-compile"),
mojoStartedLogMessage(props, "maven-resources-plugin", "testResources", "default-testResources"),
mojoStartedLogMessage(props, "maven-compiler-plugin", "testCompile", "default-testCompile"),
mojoStartedLogMessage(props, "maven-surefire-plugin", "test", "default-test"),
mojoStartedLogMessage(props, "maven-install-plugin", "install", "default-install"),
"BUILD SUCCESS"));
assertJVM(o, props);
@@ -84,4 +86,10 @@ public class SingleModuleNativeIT {
protected void assertJVM(TestClientOutput o, Properties props) {
/* implemented in the subclass */
}
String mojoStartedLogMessage(Properties props, String pluginArtifactId, String mojo, String executionId) {
return "\\Q--- " + pluginArtifactId + ":" + props.getProperty(pluginArtifactId + ".version") + ":" + mojo + " ("
+ executionId + ") @ single-module ---\\E";
}
}

View File

@@ -35,21 +35,19 @@ public class SingleModuleTest extends SingleModuleNativeIT {
Assertions.assertThat(filteredMessages)
.is(new MatchInOrderAmongOthers<>(
"\\Q:single-module:org.apache.maven.plugins:" + MvndTestUtil.plugin(props, "maven-clean-plugin")
+ ":clean {execution: default-clean}\\E",
"\\Q:single-module:org.apache.maven.plugins:" + MvndTestUtil.plugin(props, "maven-resources-plugin")
+ ":resources {execution: default-resources}\\E",
"\\Q:single-module:org.apache.maven.plugins:" + MvndTestUtil.plugin(props, "maven-compiler-plugin")
+ ":compile {execution: default-compile}\\E",
"\\Q:single-module:org.apache.maven.plugins:" + MvndTestUtil.plugin(props, "maven-resources-plugin")
+ ":testResources {execution: default-testResources}\\E",
"\\Q:single-module:org.apache.maven.plugins:" + MvndTestUtil.plugin(props, "maven-compiler-plugin")
+ ":testCompile {execution: default-testCompile}\\E",
"\\Q:single-module:org.apache.maven.plugins:" + MvndTestUtil.plugin(props, "maven-surefire-plugin")
+ ":test {execution: default-test}\\E",
"\\Q:single-module:org.apache.maven.plugins:" + MvndTestUtil.plugin(props, "maven-install-plugin")
+ ":install {execution: default-install}\\E"));
mojoStarted(props, "maven-clean-plugin", "clean", "default-clean"),
mojoStarted(props, "maven-resources-plugin", "resources", "default-resources"),
mojoStarted(props, "maven-compiler-plugin", "compile", "default-compile"),
mojoStarted(props, "maven-resources-plugin", "testResources", "default-testResources"),
mojoStarted(props, "maven-compiler-plugin", "testCompile", "default-testCompile"),
mojoStarted(props, "maven-surefire-plugin", "test", "default-test"),
mojoStarted(props, "maven-install-plugin", "install", "default-install")));
}
String mojoStarted(Properties props, String pluginArtifactId, String mojo, String executionId) {
return "\\Q" + Message.mojoStarted("single-module", "org.apache.maven.plugins", pluginArtifactId,
props.getProperty(pluginArtifactId + ".version"), mojo, executionId).toString() + "\\E";
}
}