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");
     }
 }