You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by vk...@apache.org on 2020/12/21 00:22:16 UTC
[ignite-3] branch main updated: Added progress bar to the CLI tool
This is an automated email from the ASF dual-hosted git repository.
vkulichenko pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/ignite-3.git
The following commit(s) were added to refs/heads/main by this push:
new 77539e9 Added progress bar to the CLI tool
77539e9 is described below
commit 77539e90e29c941b47b121e504c6a9789133ed76
Author: Valentin Kulichenko <va...@gmail.com>
AuthorDate: Sun Dec 20 16:21:51 2020 -0800
Added progress bar to the CLI tool
---
modules/cli-demo/cli/pom.xml | 6 ++
.../progressbar/IgniteProgressBarRenderer.java | 43 ++++++++
.../org/apache/ignite/cli/IgniteProgressBar.java | 96 ++++++++++++++++++
.../src/main/java/org/apache/ignite/cli/Table.java | 30 +++++-
.../cli/builtins/module/MavenArtifactResolver.java | 109 ++++++++++++---------
.../ignite/cli/builtins/node/NodeManager.java | 32 ++++--
.../apache/ignite/cli/spec/NodeCommandSpec.java | 3 +-
.../java/org/apache/ignite/app/IgniteRunner.java | 23 ++++-
8 files changed, 278 insertions(+), 64 deletions(-)
diff --git a/modules/cli-demo/cli/pom.xml b/modules/cli-demo/cli/pom.xml
index e73714f..59b458e 100644
--- a/modules/cli-demo/cli/pom.xml
+++ b/modules/cli-demo/cli/pom.xml
@@ -103,6 +103,12 @@
<artifactId>ascii-table</artifactId>
<version>1.1.0</version>
</dependency>
+
+ <dependency>
+ <groupId>me.tongfei</groupId>
+ <artifactId>progressbar</artifactId>
+ <version>0.9.0</version>
+ </dependency>
</dependencies>
<build>
diff --git a/modules/cli-demo/cli/src/main/java/me/tongfei/progressbar/IgniteProgressBarRenderer.java b/modules/cli-demo/cli/src/main/java/me/tongfei/progressbar/IgniteProgressBarRenderer.java
new file mode 100644
index 0000000..48f865d
--- /dev/null
+++ b/modules/cli-demo/cli/src/main/java/me/tongfei/progressbar/IgniteProgressBarRenderer.java
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package me.tongfei.progressbar;
+
+/**
+ * Custom renderer for the {@link ProgressBar}.
+ *
+ * Located in the {@code me.tongfei.progressbar} package because
+ * the required {@link ProgressState} class is package-private.
+ */
+public class IgniteProgressBarRenderer implements ProgressBarRenderer {
+ @Override public String render(ProgressState progress, int maxLength) {
+ int completed = (int)(progress.getNormalizedProgress() * 100);
+
+ StringBuilder sb = new StringBuilder("|");
+
+ sb.append("=".repeat(completed));
+
+ if (completed < 100)
+ sb.append('>').append(" ".repeat(99 - completed));
+
+ String percentage = completed + "%";
+
+ sb.append("|").append(" ".repeat(5 - percentage.length())).append(percentage);
+
+ return sb.toString();
+ }
+}
diff --git a/modules/cli-demo/cli/src/main/java/org/apache/ignite/cli/IgniteProgressBar.java b/modules/cli-demo/cli/src/main/java/org/apache/ignite/cli/IgniteProgressBar.java
new file mode 100644
index 0000000..a6bb0a0
--- /dev/null
+++ b/modules/cli-demo/cli/src/main/java/org/apache/ignite/cli/IgniteProgressBar.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ignite.cli;
+
+import java.time.Duration;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+import me.tongfei.progressbar.ConsoleProgressBarConsumer;
+import me.tongfei.progressbar.IgniteProgressBarRenderer;
+import me.tongfei.progressbar.ProgressBar;
+
+/**
+ * Basic implementation of a progress bar. Based on
+ * <a href="https://github.com/ctongfei/progressbar">https://github.com/ctongfei/progressbar</a>.
+ */
+public class IgniteProgressBar implements AutoCloseable {
+ private final ProgressBar impl;
+
+ private ScheduledExecutorService exec;
+
+ /**
+ * Creates a new progress bar.
+ *
+ * @param initialMax Initial maximum number of steps.
+ */
+ public IgniteProgressBar(long initialMax) {
+ impl = new ProgressBar(
+ null,
+ initialMax,
+ 10,
+ 0,
+ Duration.ZERO,
+ new IgniteProgressBarRenderer(),
+ new ConsoleProgressBarConsumer(System.out, 107)
+ );
+ }
+
+ /**
+ * Performs a single step.
+ */
+ public void step() {
+ impl.step();
+ }
+
+ /**
+ * Performs a single step every N milliseconds.
+ *
+ * @param interval Interval in milliseconds.
+ */
+ public synchronized void stepPeriodically(long interval) {
+ if (exec == null)
+ exec = Executors.newSingleThreadScheduledExecutor();
+
+ exec.scheduleAtFixedRate(impl::step, interval, interval, TimeUnit.MILLISECONDS);
+ }
+
+ /**
+ * Updates maximum number of steps.
+ *
+ * @param newMax New maximum.
+ */
+ public void setMax(long newMax) {
+ impl.maxHint(newMax);
+ }
+
+ @Override public void close() {
+ while (impl.getCurrent() < impl.getMax()) {
+ try {
+ Thread.sleep(10);
+ }
+ catch (InterruptedException ignored) {
+ break;
+ }
+
+ step();
+ }
+
+ impl.close();
+ }
+}
diff --git a/modules/cli-demo/cli/src/main/java/org/apache/ignite/cli/Table.java b/modules/cli-demo/cli/src/main/java/org/apache/ignite/cli/Table.java
index 5a0f6cb..af43004 100644
--- a/modules/cli-demo/cli/src/main/java/org/apache/ignite/cli/Table.java
+++ b/modules/cli-demo/cli/src/main/java/org/apache/ignite/cli/Table.java
@@ -22,6 +22,9 @@ import java.util.Collection;
import picocli.CommandLine.Help.Ansi.Text;
import picocli.CommandLine.Help.ColorScheme;
+/**
+ * Basic implementation of an ascii table. Supports styling via {@link ColorScheme}.
+ */
public class Table {
private final int indent;
@@ -31,6 +34,13 @@ public class Table {
private int[] lengths;
+ /**
+ * Creates a new table.
+ *
+ * @param indent Left-side indentation (i.e. the provided
+ * number of spaces will be added to every line in the output).
+ * @param colorScheme Color scheme.
+ */
public Table(int indent, ColorScheme colorScheme) {
if (indent < 0)
throw new IllegalArgumentException("Indent can't be negative.");
@@ -39,6 +49,13 @@ public class Table {
this.colorScheme = colorScheme;
}
+ /**
+ * Adds a row.
+ *
+ * @param items List of items in the row. Every item is converted to a string
+ * and styled based on the provided {@link ColorScheme}.
+ * If an instance of {@link Text} is provided, it is added as-is.
+ */
public void addRow(Object... items) {
if (lengths == null) {
lengths = new int[items.length];
@@ -50,16 +67,23 @@ public class Table {
Text[] row = new Text[items.length];
for (int i = 0; i < items.length; i++) {
- Text item = colorScheme.text(items[i].toString());
+ Object item = items[i];
+
+ Text text = item instanceof Text ? (Text)item : colorScheme.text(item.toString());
- row[i] = item;
+ row[i] = text;
- lengths[i] = Math.max(lengths[i], item.getCJKAdjustedLength());
+ lengths[i] = Math.max(lengths[i], text.getCJKAdjustedLength());
}
data.add(row);
}
+ /**
+ * Converts the table to a string.
+ *
+ * @return String representation of this table.
+ */
@Override public String toString() {
String indentStr = " ".repeat(indent);
diff --git a/modules/cli-demo/cli/src/main/java/org/apache/ignite/cli/builtins/module/MavenArtifactResolver.java b/modules/cli-demo/cli/src/main/java/org/apache/ignite/cli/builtins/module/MavenArtifactResolver.java
index 6aa026d..f20e846 100644
--- a/modules/cli-demo/cli/src/main/java/org/apache/ignite/cli/builtins/module/MavenArtifactResolver.java
+++ b/modules/cli-demo/cli/src/main/java/org/apache/ignite/cli/builtins/module/MavenArtifactResolver.java
@@ -29,10 +29,15 @@ import java.util.stream.Collectors;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.apache.ignite.cli.IgniteCLIException;
+import org.apache.ignite.cli.IgniteProgressBar;
import org.apache.ignite.cli.builtins.SystemPathResolver;
import org.apache.ivy.Ivy;
import org.apache.ivy.core.IvyContext;
import org.apache.ivy.core.event.EventManager;
+import org.apache.ivy.core.event.download.EndArtifactDownloadEvent;
+import org.apache.ivy.core.event.resolve.EndResolveDependencyEvent;
+import org.apache.ivy.core.event.resolve.EndResolveEvent;
+import org.apache.ivy.core.event.retrieve.EndRetrieveArtifactEvent;
import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
@@ -75,49 +80,61 @@ public class MavenArtifactResolver {
List<URL> customRepositories
) throws IOException {
Ivy ivy = ivyInstance(customRepositories); // needed for init right output logger before any operations
- out.print("Resolve artifact " + grpId + ":" +
- artifactId + ":" + version);
- ModuleDescriptor md = rootModuleDescriptor(grpId, artifactId, version);
-
- // Step 1: you always need to resolve before you can retrieve
- //
- ResolveOptions ro = new ResolveOptions();
- // this seems to have no impact, if you resolve by module descriptor
- //
- // (in contrast to resolve by ModuleRevisionId)
- ro.setTransitive(true);
- // if set to false, nothing will be downloaded
- ro.setDownload(true);
- try {
- // now resolve
- ResolveReport rr = ivy.resolve(md,ro);
-
- if (rr.hasError())
- throw new IgniteCLIException(rr.getAllProblemMessages().toString());
-
- // Step 2: retrieve
- ModuleDescriptor m = rr.getModuleDescriptor();
-
- RetrieveReport retrieveReport = ivy.retrieve(
- m.getModuleRevisionId(),
- new RetrieveOptions()
- // this is from the envelop module
- .setConfs(new String[] {"default"})
- .setDestArtifactPattern(mavenRoot.toFile().getAbsolutePath() + "/[artifact](-[classifier]).[revision].[ext]")
- );
-
-
- return new ResolveResult(
- retrieveReport.getRetrievedFiles().stream().map(File::toPath).collect(Collectors.toList())
- );
- }
- catch (ParseException e) {
- // TOOD
- throw new IOException(e);
- }
- finally {
- out.println();
+ try (IgniteProgressBar bar = new IgniteProgressBar(100)) {
+ ivy.getEventManager().addIvyListener(event -> {
+ if (event instanceof EndResolveEvent) {
+ int count = ((EndResolveEvent)event).getReport().getArtifacts().size();
+
+ bar.setMax(count * 3);
+ }
+ else if (event instanceof EndArtifactDownloadEvent ||
+ event instanceof EndResolveDependencyEvent ||
+ event instanceof EndRetrieveArtifactEvent) {
+ bar.step();
+ }
+ });
+
+ //out.print("Resolve artifact " + grpId + ":" + artifactId + ":" + version);
+
+ ModuleDescriptor md = rootModuleDescriptor(grpId, artifactId, version);
+
+ // Step 1: you always need to resolve before you can retrieve
+ //
+ ResolveOptions ro = new ResolveOptions();
+ // this seems to have no impact, if you resolve by module descriptor
+ //
+ // (in contrast to resolve by ModuleRevisionId)
+ ro.setTransitive(true);
+ // if set to false, nothing will be downloaded
+ ro.setDownload(true);
+
+ try {
+ // now resolve
+ ResolveReport rr = ivy.resolve(md, ro);
+
+ if (rr.hasError())
+ throw new IgniteCLIException(rr.getAllProblemMessages().toString());
+
+ // Step 2: retrieve
+ ModuleDescriptor m = rr.getModuleDescriptor();
+
+ RetrieveReport retrieveReport = ivy.retrieve(
+ m.getModuleRevisionId(),
+ new RetrieveOptions()
+ // this is from the envelop module
+ .setConfs(new String[]{"default"})
+ .setDestArtifactPattern(mavenRoot.toFile().getAbsolutePath() + "/[artifact](-[classifier]).[revision].[ext]")
+ );
+
+ return new ResolveResult(
+ retrieveReport.getRetrievedFiles().stream().map(File::toPath).collect(Collectors.toList())
+ );
+ }
+ catch (ParseException e) {
+ // TOOD
+ throw new IOException(e);
+ }
}
}
@@ -132,10 +149,10 @@ public class MavenArtifactResolver {
tmpDir.deleteOnExit();
EventManager eventManager = new EventManager();
- eventManager.addIvyListener(event -> {
- out.print(".");
- out.flush();
- });
+// eventManager.addIvyListener(event -> {
+// out.print(".");
+// out.flush();
+// });
IvySettings ivySettings = new IvySettings();
ivySettings.setDefaultCache(tmpDir);
diff --git a/modules/cli-demo/cli/src/main/java/org/apache/ignite/cli/builtins/node/NodeManager.java b/modules/cli-demo/cli/src/main/java/org/apache/ignite/cli/builtins/node/NodeManager.java
index 13afe4c..4f4e992 100644
--- a/modules/cli-demo/cli/src/main/java/org/apache/ignite/cli/builtins/node/NodeManager.java
+++ b/modules/cli-demo/cli/src/main/java/org/apache/ignite/cli/builtins/node/NodeManager.java
@@ -26,7 +26,7 @@ import java.nio.file.StandardWatchEventKinds;
import java.nio.file.WatchEvent;
import java.nio.file.WatchService;
import java.time.Duration;
-import java.util.Arrays;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
@@ -35,8 +35,9 @@ import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.inject.Inject;
import javax.inject.Singleton;
-import org.apache.ignite.cli.builtins.module.ModuleStorage;
import org.apache.ignite.cli.IgniteCLIException;
+import org.apache.ignite.cli.IgniteProgressBar;
+import org.apache.ignite.cli.builtins.module.ModuleStorage;
@Singleton
public class NodeManager {
@@ -64,19 +65,30 @@ public class NodeManager {
Files.createFile(logFile);
- var commandArgs = Arrays.asList(
- "java", "-cp", classpath(), MAIN_CLASS,
- "--config", serverConfig.toAbsolutePath().toString()
- );
+ var commandArgs = new ArrayList<String>();
+
+ commandArgs.add("java");
+ commandArgs.add("-cp");
+ commandArgs.add(classpath());
+ commandArgs.add(MAIN_CLASS);
+
+ if (serverConfig != null) {
+ commandArgs.add("--config");
+ commandArgs.add(serverConfig.toAbsolutePath().toString());
+ }
ProcessBuilder pb = new ProcessBuilder(
commandArgs
)
.redirectError(logFile.toFile())
.redirectOutput(logFile.toFile());
+
Process p = pb.start();
- try {
- if (!waitForStart("Ignite application started successfully", logFile, NODE_START_TIMEOUT)) {
+
+ try (var bar = new IgniteProgressBar(100)) {
+ bar.stepPeriodically(300);
+
+ if (!waitForStart("Apache Ignite started successfully!", logFile, NODE_START_TIMEOUT)) {
p.destroyForcibly();
throw new IgniteCLIException("Node wasn't started during timeout period "
+ NODE_START_TIMEOUT.toMillis() + "ms");
@@ -85,7 +97,9 @@ public class NodeManager {
catch (InterruptedException|IOException e) {
throw new IgniteCLIException("Waiting for node start was failed", e);
}
+
createPidFile(consistentId, p.pid(), pidsDir);
+
return new RunningNode(p.pid(), consistentId, logFile);
}
catch (IOException e) {
@@ -211,7 +225,7 @@ public class NodeManager {
.map(ProcessHandle::destroy)
.orElse(false);
}
-
+
private static Path logFile(Path workDir, String consistentId) {
return workDir.resolve(consistentId + ".log");
}
diff --git a/modules/cli-demo/cli/src/main/java/org/apache/ignite/cli/spec/NodeCommandSpec.java b/modules/cli-demo/cli/src/main/java/org/apache/ignite/cli/spec/NodeCommandSpec.java
index cc13eac..5b761f3 100644
--- a/modules/cli-demo/cli/src/main/java/org/apache/ignite/cli/spec/NodeCommandSpec.java
+++ b/modules/cli-demo/cli/src/main/java/org/apache/ignite/cli/spec/NodeCommandSpec.java
@@ -21,7 +21,6 @@ import java.io.IOException;
import java.nio.file.Path;
import java.util.List;
import javax.inject.Inject;
-import io.micronaut.context.ApplicationContext;
import org.apache.ignite.cli.CliPathsConfigLoader;
import org.apache.ignite.cli.IgniteCLIException;
import org.apache.ignite.cli.IgnitePaths;
@@ -55,7 +54,7 @@ public class NodeCommandSpec extends AbstractCommandSpec {
@CommandLine.Parameters(paramLabel = "consistent-id", description = "ConsistentId for new node")
public String consistentId;
- @CommandLine.Option(names = {"--config"}, required = true,
+ @CommandLine.Option(names = {"--config"},
description = "path to configuration file")
public Path configPath;
diff --git a/modules/ignite-runner/src/main/java/org/apache/ignite/app/IgniteRunner.java b/modules/ignite-runner/src/main/java/org/apache/ignite/app/IgniteRunner.java
index ef197c1..02d8877 100644
--- a/modules/ignite-runner/src/main/java/org/apache/ignite/app/IgniteRunner.java
+++ b/modules/ignite-runner/src/main/java/org/apache/ignite/app/IgniteRunner.java
@@ -32,6 +32,21 @@ import org.slf4j.LoggerFactory;
*/
public class IgniteRunner {
/** */
+ private static final String BANNER = "\n" +
+ " ___ __\n" +
+ " / | ____ ____ _ _____ / /_ ___\n" +
+ " ⣠⣶⣿ / /| | / __ \\ / __ `// ___// __ \\ / _ \\\n" +
+ " ⣿⣿⣿⣿ / ___ | / /_/ // /_/ // /__ / / / // __/\n" +
+ " ⢠⣿⡏⠈⣿⣿⣿⣿⣷ /_/ |_|/ .___/ \\__,_/ \\___//_/ /_/ \\___/\n" +
+ " ⢰⣿⣿⣿⣧⠈⢿⣿⣿⣿⣿⣦ /_/\n" +
+ " ⠘⣿⣿⣿⣿⣿⣦⠈⠛⢿⣿⣿⣿⡄ ____ _ __ _____\n" +
+ " ⠈⠛⣿⣿⣿⣿⣿⣿⣦⠉⢿⣿⡟ / _/____ _ ____ (_)/ /_ ___ |__ /\n" +
+ " ⢰⣿⣶⣀⠈⠙⠿⣿⣿⣿⣿ ⠟⠁ / / / __ `// __ \\ / // __// _ \\ /_ <\n" +
+ " ⠈⠻⣿⣿⣿⣿⣷⣤⠙⢿⡟ _/ / / /_/ // / / // // /_ / __/ ___/ /\n" +
+ " ⠉⠉⠛⠏⠉ /___/ \\__, //_/ /_//_/ \\__/ \\___/ /____/\n" +
+ " /____/\n\n";
+
+ /** */
private static final String CONF_PARAM_NAME = "--config";
/** */
@@ -51,7 +66,7 @@ public class IgniteRunner {
* @param args Empty or providing path to custom configuration file after marker parameter "--config".
*/
public static void main(String[] args) throws IOException {
- ackVersion();
+ ackBanner();
ConfigurationModule confModule = new ConfigurationModule();
@@ -89,13 +104,13 @@ public class IgniteRunner {
/** */
private static void ackSuccessStart() {
- log.info("Ignite application started successfully");
+ log.info("Apache Ignite started successfully!");
}
/** */
- private static void ackVersion() {
+ private static void ackBanner() {
String ver = IgniteProperties.get(VER_KEY);
- log.info("Starting Ignite of version " + ver);
+ log.info(BANNER + "Apache Ignite ver. " + ver + "\n");
}
}