From a9a3e907ee8cca26d2b9899bcff4213bc214768c Mon Sep 17 00:00:00 2001 From: Peter Palaga Date: Wed, 28 Oct 2020 23:55:08 +0100 Subject: [PATCH] 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 --- .../fuse/mvnd/client/DaemonConnector.java | 15 +- .../jboss/fuse/mvnd/client/DefaultClient.java | 3 +- .../mvnd/common/logging/ClientOutput.java | 2 + .../mvnd/common/logging/TerminalOutput.java | 137 ++++++++++++------ .../fuse/mvnd/plugin/CliPluginRealmCache.java | 2 - 5 files changed, 101 insertions(+), 58 deletions(-) diff --git a/client/src/main/java/org/jboss/fuse/mvnd/client/DaemonConnector.java b/client/src/main/java/org/jboss/fuse/mvnd/client/DaemonConnector.java index 65df617d..77b91e52 100644 --- a/client/src/main/java/org/jboss/fuse/mvnd/client/DaemonConnector.java +++ b/client/src/main/java/org/jboss/fuse/mvnd/client/DaemonConnector.java @@ -15,7 +15,6 @@ */ package org.jboss.fuse.mvnd.client; -import java.io.File; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; @@ -30,7 +29,6 @@ import java.util.Map; import java.util.Optional; import java.util.UUID; import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; import java.util.stream.Collectors; import org.jboss.fuse.mvnd.common.BuildProperties; 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.MavenDaemon; import org.jboss.fuse.mvnd.common.Os; +import org.jboss.fuse.mvnd.common.logging.ClientOutput; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -86,10 +85,10 @@ public class DaemonConnector { return null; } - public DaemonClientConnection connect(DaemonCompatibilitySpec constraint, Consumer logger) { + public DaemonClientConnection connect(DaemonCompatibilitySpec constraint, ClientOutput output) { + output.buildStatus("Looking up daemon..."); Map> idleBusy = registry.getAll().stream() .collect(Collectors.groupingBy(di -> di.getState() == DaemonState.Idle)); - final Collection idleDaemons = idleBusy.getOrDefault(true, Collections.emptyList()); final Collection busyDaemons = idleBusy.getOrDefault(false, Collections.emptyList()); @@ -107,7 +106,7 @@ public class DaemonConnector { // No compatible daemons available - start a new daemon String message = handleStopEvents(idleDaemons, busyDaemons); - logger.accept(message); + output.buildStatus(message); return startDaemon(constraint); } @@ -151,11 +150,11 @@ public class DaemonConnector { if (numStopped > 0) { reasons.add(numStopped + " stopped"); } - return "Starting a Maven Daemon, " - + String.join(" and ", reasons) + " Daemon" + (totalUnavailableDaemons > 1 ? "s" : "") + return "Starting new daemon, " + + String.join(" and ", reasons) + " daemon" + (totalUnavailableDaemons > 1 ? "s" : "") + " could not be reused, use --status for details"; } else { - return "Starting a Maven Daemon (subsequent builds will be faster)"; + return "Starting new daemon (subsequent builds will be faster)..."; } } diff --git a/client/src/main/java/org/jboss/fuse/mvnd/client/DefaultClient.java b/client/src/main/java/org/jboss/fuse/mvnd/client/DefaultClient.java index c373594c..71de913a 100644 --- a/client/src/main/java/org/jboss/fuse/mvnd/client/DefaultClient.java +++ b/client/src/main/java/org/jboss/fuse/mvnd/client/DefaultClient.java @@ -202,8 +202,7 @@ public class DefaultClient implements Client { final DaemonConnector connector = new DaemonConnector(layout, registry, buildProperties); List opts = new ArrayList<>(); - try (DaemonClientConnection daemon = connector.connect(new DaemonCompatibilitySpec(javaHome, opts), - s -> output.accept(null, s))) { + try (DaemonClientConnection daemon = connector.connect(new DaemonCompatibilitySpec(javaHome, opts), output)) { daemon.dispatch(new Message.BuildRequest( args, diff --git a/common/src/main/java/org/jboss/fuse/mvnd/common/logging/ClientOutput.java b/common/src/main/java/org/jboss/fuse/mvnd/common/logging/ClientOutput.java index fd17b83f..7dcb832f 100644 --- a/common/src/main/java/org/jboss/fuse/mvnd/common/logging/ClientOutput.java +++ b/common/src/main/java/org/jboss/fuse/mvnd/common/logging/ClientOutput.java @@ -36,4 +36,6 @@ public interface ClientOutput extends AutoCloseable { void keepAlive(); + void buildStatus(String status); + } diff --git a/common/src/main/java/org/jboss/fuse/mvnd/common/logging/TerminalOutput.java b/common/src/main/java/org/jboss/fuse/mvnd/common/logging/TerminalOutput.java index 4848a010..729a9835 100644 --- a/common/src/main/java/org/jboss/fuse/mvnd/common/logging/TerminalOutput.java +++ b/common/src/main/java/org/jboss/fuse/mvnd/common/logging/TerminalOutput.java @@ -64,13 +64,14 @@ public class TerminalOutput implements ClientOutput { private boolean displayDone = false; private final long start; + private String buildStatus; private String name; private int totalProjects; private int doneProjects; - private int usedCores; + private int maxThreads; enum EventType { - BUILD, + BUILD_STATUS, PROJECT_STATE, PROJECT_FINISHED, 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 { final String id; String status; @@ -125,7 +130,7 @@ public class TerminalOutput implements ClientOutput { this.name = name; this.totalProjects = projects; this.doneProjects = 0; - this.usedCores = cores; + this.maxThreads = cores; } 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() { try { while (!closing) { @@ -209,6 +223,10 @@ public class TerminalOutput implements ClientOutput { queue.drainTo(entries); for (Event entry : entries) { switch (entry.type) { + case BUILD_STATUS: { + this.buildStatus = entry.message; + break; + } case END_OF_STREAM: { projects.values().stream().flatMap(p -> p.log.stream()).forEach(log); clearDisplay(); @@ -312,49 +330,16 @@ public class TerminalOutput implements ClientOutput { return; } final List 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 - if (projects.size() <= dispLines) { - if (name != null) { - AttributedStringBuilder asb = new AttributedStringBuilder(); - asb.append("Building "); - asb.style(AttributedStyle.BOLD); - asb.append(name); - asb.style(AttributedStyle.DEFAULT); + final int projectsCount = projects.size(); - StringBuilder statusLine = new StringBuilder(64); - statusLine.append(" threads: ").append(usedCores); + addStatusLine(lines, dispLines, projectsCount); - 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'); - } - - 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(); + if (projectsCount <= dispLines) { + int remLogLines = dispLines - projectsCount; for (Project prj : projects.values()) { - String str = prj.status != null ? prj.status : ":" + prj.id + ":"; - 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)); - } + addProjectLine(lines, prj); // get the last lines of the project log, taking multi-line logs into account int nb = Math.min(remLogLines, linesPerProject); List logs = lastN(prj.log, nb).stream() @@ -365,10 +350,14 @@ public class TerminalOutput implements ClientOutput { remLogLines -= logs.size(); } } else { - lines.add(new AttributedString("Building... (" + (projects.size() - dispLines) + " more)")); - lines.addAll(projects.values().stream() - .map(prj -> AttributedString.fromAnsi(prj.status != null ? prj.status : "")) - .collect(lastN(dispLines))); + int skipProjects = projectsCount - dispLines; + for (Project prj : projects.values()) { + if (skipProjects == 0) { + addProjectLine(lines, prj); + } else { + skipProjects--; + } + } } List trimmed = lines.stream() .map(s -> s.columnSubSequence(0, cols)) @@ -376,6 +365,62 @@ public class TerminalOutput implements ClientOutput { display.update(trimmed, -1); } + private void addStatusLine(final List 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 lines, Project prj) { + String str = prj.status != null ? prj.status : ":" + prj.id + ":"; + 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 List lastN(List list, int n) { return list.subList(Math.max(0, list.size() - n), list.size()); } diff --git a/daemon/src/main/java/org/jboss/fuse/mvnd/plugin/CliPluginRealmCache.java b/daemon/src/main/java/org/jboss/fuse/mvnd/plugin/CliPluginRealmCache.java index 05ee005a..9d49044d 100644 --- a/daemon/src/main/java/org/jboss/fuse/mvnd/plugin/CliPluginRealmCache.java +++ b/daemon/src/main/java/org/jboss/fuse/mvnd/plugin/CliPluginRealmCache.java @@ -38,11 +38,9 @@ import java.util.Map.Entry; import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; - import javax.enterprise.inject.Default; import javax.inject.Named; import javax.inject.Singleton; - import org.apache.maven.RepositoryUtils; import org.apache.maven.artifact.Artifact; import org.apache.maven.eventspy.AbstractEventSpy;