Improve terminal output

* Say 'Looking for daemon...' straight ahead to elliminate the initial
  lag
* Present 'hidden' threads along with used/max threads instead of
  'n more'
* Print project name in bold even if no mojo is present in the line
* Be consistent about printing project artifactIds in bold also when
  lines are hidden
* The righ status line is present also when lines are hidden
This commit is contained in:
Peter Palaga
2020-10-28 23:55:08 +01:00
parent 1bb009127b
commit a9a3e907ee
5 changed files with 101 additions and 58 deletions

View File

@@ -15,7 +15,6 @@
*/ */
package org.jboss.fuse.mvnd.client; package org.jboss.fuse.mvnd.client;
import java.io.File;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.Socket; import java.net.Socket;
@@ -30,7 +29,6 @@ import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.jboss.fuse.mvnd.common.BuildProperties; import org.jboss.fuse.mvnd.common.BuildProperties;
import org.jboss.fuse.mvnd.common.DaemonCompatibilitySpec; import org.jboss.fuse.mvnd.common.DaemonCompatibilitySpec;
@@ -45,6 +43,7 @@ import org.jboss.fuse.mvnd.common.DaemonStopEvent;
import org.jboss.fuse.mvnd.common.Environment; import org.jboss.fuse.mvnd.common.Environment;
import org.jboss.fuse.mvnd.common.MavenDaemon; import org.jboss.fuse.mvnd.common.MavenDaemon;
import org.jboss.fuse.mvnd.common.Os; import org.jboss.fuse.mvnd.common.Os;
import org.jboss.fuse.mvnd.common.logging.ClientOutput;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@@ -86,10 +85,10 @@ public class DaemonConnector {
return null; return null;
} }
public DaemonClientConnection connect(DaemonCompatibilitySpec constraint, Consumer<String> logger) { public DaemonClientConnection connect(DaemonCompatibilitySpec constraint, ClientOutput output) {
output.buildStatus("Looking up daemon...");
Map<Boolean, List<DaemonInfo>> idleBusy = registry.getAll().stream() Map<Boolean, List<DaemonInfo>> idleBusy = registry.getAll().stream()
.collect(Collectors.groupingBy(di -> di.getState() == DaemonState.Idle)); .collect(Collectors.groupingBy(di -> di.getState() == DaemonState.Idle));
final Collection<DaemonInfo> idleDaemons = idleBusy.getOrDefault(true, Collections.emptyList()); final Collection<DaemonInfo> idleDaemons = idleBusy.getOrDefault(true, Collections.emptyList());
final Collection<DaemonInfo> busyDaemons = idleBusy.getOrDefault(false, Collections.emptyList()); final Collection<DaemonInfo> busyDaemons = idleBusy.getOrDefault(false, Collections.emptyList());
@@ -107,7 +106,7 @@ public class DaemonConnector {
// No compatible daemons available - start a new daemon // No compatible daemons available - start a new daemon
String message = handleStopEvents(idleDaemons, busyDaemons); String message = handleStopEvents(idleDaemons, busyDaemons);
logger.accept(message); output.buildStatus(message);
return startDaemon(constraint); return startDaemon(constraint);
} }
@@ -151,11 +150,11 @@ public class DaemonConnector {
if (numStopped > 0) { if (numStopped > 0) {
reasons.add(numStopped + " stopped"); reasons.add(numStopped + " stopped");
} }
return "Starting a Maven Daemon, " return "Starting new daemon, "
+ String.join(" and ", reasons) + " Daemon" + (totalUnavailableDaemons > 1 ? "s" : "") + String.join(" and ", reasons) + " daemon" + (totalUnavailableDaemons > 1 ? "s" : "")
+ " could not be reused, use --status for details"; + " could not be reused, use --status for details";
} else { } else {
return "Starting a Maven Daemon (subsequent builds will be faster)"; return "Starting new daemon (subsequent builds will be faster)...";
} }
} }

View File

@@ -202,8 +202,7 @@ public class DefaultClient implements Client {
final DaemonConnector connector = new DaemonConnector(layout, registry, buildProperties); final DaemonConnector connector = new DaemonConnector(layout, registry, buildProperties);
List<String> opts = new ArrayList<>(); List<String> opts = new ArrayList<>();
try (DaemonClientConnection daemon = connector.connect(new DaemonCompatibilitySpec(javaHome, opts), try (DaemonClientConnection daemon = connector.connect(new DaemonCompatibilitySpec(javaHome, opts), output)) {
s -> output.accept(null, s))) {
daemon.dispatch(new Message.BuildRequest( daemon.dispatch(new Message.BuildRequest(
args, args,

View File

@@ -36,4 +36,6 @@ public interface ClientOutput extends AutoCloseable {
void keepAlive(); void keepAlive();
void buildStatus(String status);
} }

View File

@@ -64,13 +64,14 @@ public class TerminalOutput implements ClientOutput {
private boolean displayDone = false; private boolean displayDone = false;
private final long start; private final long start;
private String buildStatus;
private String name; private String name;
private int totalProjects; private int totalProjects;
private int doneProjects; private int doneProjects;
private int usedCores; private int maxThreads;
enum EventType { enum EventType {
BUILD, BUILD_STATUS,
PROJECT_STATE, PROJECT_STATE,
PROJECT_FINISHED, PROJECT_FINISHED,
LOG, LOG,
@@ -92,6 +93,10 @@ public class TerminalOutput implements ClientOutput {
} }
} }
/**
* {@link Project} is owned by the display loop thread and is accessed only from there. Therefore it does not need
* to be immutable.
*/
static class Project { static class Project {
final String id; final String id;
String status; String status;
@@ -125,7 +130,7 @@ public class TerminalOutput implements ClientOutput {
this.name = name; this.name = name;
this.totalProjects = projects; this.totalProjects = projects;
this.doneProjects = 0; this.doneProjects = 0;
this.usedCores = cores; this.maxThreads = cores;
} }
public void projectStateChanged(String projectId, String task) { public void projectStateChanged(String projectId, String task) {
@@ -182,6 +187,15 @@ public class TerminalOutput implements ClientOutput {
} }
} }
@Override
public void buildStatus(String status) {
try {
queue.put(new Event(EventType.BUILD_STATUS, null, status));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
void readInputLoop() { void readInputLoop() {
try { try {
while (!closing) { while (!closing) {
@@ -209,6 +223,10 @@ public class TerminalOutput implements ClientOutput {
queue.drainTo(entries); queue.drainTo(entries);
for (Event entry : entries) { for (Event entry : entries) {
switch (entry.type) { switch (entry.type) {
case BUILD_STATUS: {
this.buildStatus = entry.message;
break;
}
case END_OF_STREAM: { case END_OF_STREAM: {
projects.values().stream().flatMap(p -> p.log.stream()).forEach(log); projects.values().stream().flatMap(p -> p.log.stream()).forEach(log);
clearDisplay(); clearDisplay();
@@ -312,49 +330,16 @@ public class TerminalOutput implements ClientOutput {
return; return;
} }
final List<AttributedString> lines = new ArrayList<>(rows); final List<AttributedString> lines = new ArrayList<>(rows);
int dispLines = rows - 1; // for the "Building..." line 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
if (projects.size() <= dispLines) { final int projectsCount = projects.size();
if (name != null) {
AttributedStringBuilder asb = new AttributedStringBuilder();
asb.append("Building ");
asb.style(AttributedStyle.BOLD);
asb.append(name);
asb.style(AttributedStyle.DEFAULT);
StringBuilder statusLine = new StringBuilder(64); addStatusLine(lines, dispLines, projectsCount);
statusLine.append(" threads: ").append(usedCores);
statusLine.append(" time: "); if (projectsCount <= dispLines) {
long sec = (System.currentTimeMillis() - this.start) / 1000; int remLogLines = dispLines - projectsCount;
if (sec > 60) {
statusLine.append(sec / 60).append('m').append(String.valueOf(sec % 60)).append('s');
} else {
statusLine.append(sec).append('s');
}
if (totalProjects > 0) {
statusLine.append(" progress: ").append(doneProjects).append('/').append(totalProjects).append(' ')
.append(doneProjects * 100 / totalProjects).append('%');
}
lines.add(asb.append(statusLine.toString()).toAttributedString());
}
int remLogLines = dispLines - projects.size();
for (Project prj : projects.values()) { for (Project prj : projects.values()) {
String str = prj.status != null ? prj.status : ":" + prj.id + ":<unknown>"; addProjectLine(lines, prj);
int cs = str.indexOf(':');
int ce = cs >= 0 ? str.indexOf(':', cs + 1) : -1;
if (ce > 0) {
AttributedStringBuilder asb = new AttributedStringBuilder();
asb.append(str, 0, cs);
asb.style(AttributedStyle.BOLD);
asb.append(str, cs, ce);
asb.style(AttributedStyle.DEFAULT);
asb.append(str, ce, str.length());
lines.add(asb.toAttributedString());
} else {
lines.add(AttributedString.fromAnsi(str));
}
// get the last lines of the project log, taking multi-line logs into account // get the last lines of the project log, taking multi-line logs into account
int nb = Math.min(remLogLines, linesPerProject); int nb = Math.min(remLogLines, linesPerProject);
List<AttributedString> logs = lastN(prj.log, nb).stream() List<AttributedString> logs = lastN(prj.log, nb).stream()
@@ -365,10 +350,14 @@ public class TerminalOutput implements ClientOutput {
remLogLines -= logs.size(); remLogLines -= logs.size();
} }
} else { } else {
lines.add(new AttributedString("Building... (" + (projects.size() - dispLines) + " more)")); int skipProjects = projectsCount - dispLines;
lines.addAll(projects.values().stream() for (Project prj : projects.values()) {
.map(prj -> AttributedString.fromAnsi(prj.status != null ? prj.status : "<unknown>")) if (skipProjects == 0) {
.collect(lastN(dispLines))); addProjectLine(lines, prj);
} else {
skipProjects--;
}
}
} }
List<AttributedString> trimmed = lines.stream() List<AttributedString> trimmed = lines.stream()
.map(s -> s.columnSubSequence(0, cols)) .map(s -> s.columnSubSequence(0, cols))
@@ -376,6 +365,62 @@ public class TerminalOutput implements ClientOutput {
display.update(trimmed, -1); display.update(trimmed, -1);
} }
private void addStatusLine(final List<AttributedString> lines, int dispLines, final int projectsCount) {
AttributedStringBuilder asb = new AttributedStringBuilder();
StringBuilder statusLine = new StringBuilder(64);
if (name == null) {
statusLine.append(buildStatus != null ? buildStatus : "Looking up daemon...");
} else {
asb.append("Building ");
asb.style(AttributedStyle.BOLD);
asb.append(name);
asb.style(AttributedStyle.DEFAULT);
if (projectsCount <= dispLines) {
statusLine.append(" threads used/max: ")
.append(projectsCount).append('/').append(maxThreads);
} else {
statusLine.append(" threads used/hidden/max: ")
.append(projectsCount).append('/').append(projectsCount - dispLines).append('/').append(maxThreads);
}
if (totalProjects > 0) {
statusLine.append(" progress: ").append(doneProjects).append('/').append(totalProjects).append(' ')
.append(doneProjects * 100 / totalProjects).append('%');
}
}
statusLine.append(" time: ");
long sec = (System.currentTimeMillis() - this.start) / 1000;
if (sec > 60) {
statusLine.append(sec / 60).append('m').append(String.valueOf(sec % 60)).append('s');
} else {
statusLine.append(sec).append('s');
}
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());
} else {
lines.add(AttributedString.fromAnsi(str));
}
}
private static <T> List<T> lastN(List<T> list, int n) { private static <T> List<T> lastN(List<T> list, int n) {
return list.subList(Math.max(0, list.size() - n), list.size()); return list.subList(Math.max(0, list.size() - n), list.size());
} }

View File

@@ -38,11 +38,9 @@ import java.util.Map.Entry;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import javax.enterprise.inject.Default; import javax.enterprise.inject.Default;
import javax.inject.Named; import javax.inject.Named;
import javax.inject.Singleton; import javax.inject.Singleton;
import org.apache.maven.RepositoryUtils; import org.apache.maven.RepositoryUtils;
import org.apache.maven.artifact.Artifact; import org.apache.maven.artifact.Artifact;
import org.apache.maven.eventspy.AbstractEventSpy; import org.apache.maven.eventspy.AbstractEventSpy;