# ignite-63

Project: http://git-wip-us.apache.org/repos/asf/incubator-ignite/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-ignite/commit/e1c3c8ce
Tree: http://git-wip-us.apache.org/repos/asf/incubator-ignite/tree/e1c3c8ce
Diff: http://git-wip-us.apache.org/repos/asf/incubator-ignite/diff/e1c3c8ce

Branch: refs/heads/ignite-63
Commit: e1c3c8ce164bb0601c3853de09da8cf3746141de
Parents: 833c1e6
Author: sboikov <sboi...@gridgain.com>
Authored: Fri Jan 23 11:14:27 2015 +0300
Committer: sboikov <sboi...@gridgain.com>
Committed: Fri Jan 23 11:14:28 2015 +0300

----------------------------------------------------------------------
 bin/ggvisorcmd.sh                               |    4 +-
 .../scala/org/apache/ignite/visor/Packet.scala  |   38 +
 .../ignite/visor/commands/VisorConsole.scala    |  344 +++
 .../visor/commands/VisorConsoleCommand.scala    |   77 +
 .../ignite/visor/commands/VisorTextTable.scala  |  539 ++++
 .../ignite/visor/commands/ack/Packet.scala      |   55 +
 .../visor/commands/ack/VisorAckCommand.scala    |  163 ++
 .../ignite/visor/commands/alert/Packet.scala    |  108 +
 .../commands/alert/VisorAlertCommand.scala      |  840 ++++++
 .../ignite/visor/commands/cache/Packet.scala    |  127 +
 .../commands/cache/VisorCacheClearCommand.scala |  151 +
 .../commands/cache/VisorCacheCommand.scala      |  894 ++++++
 .../cache/VisorCacheCompactCommand.scala        |  151 +
 .../commands/cache/VisorCacheScanCommand.scala  |  237 ++
 .../commands/cache/VisorCacheSwapCommand.scala  |  151 +
 .../ignite/visor/commands/config/Packet.scala   |   55 +
 .../config/VisorConfigurationCommand.scala      |  502 ++++
 .../ignite/visor/commands/deploy/Packet.scala   |   76 +
 .../commands/deploy/VisorDeployCommand.scala    |  613 ++++
 .../ignite/visor/commands/disco/Packet.scala    |   72 +
 .../commands/disco/VisorDiscoveryCommand.scala  |  307 ++
 .../ignite/visor/commands/events/Packet.scala   |   97 +
 .../commands/events/VisorEventsCommand.scala    |  531 ++++
 .../ignite/visor/commands/gc/Packet.scala       |   61 +
 .../visor/commands/gc/VisorGcCommand.scala      |  245 ++
 .../ignite/visor/commands/ggcube_128x128.png    |  Bin 0 -> 11444 bytes
 .../ignite/visor/commands/ggcube_48x48.png      |  Bin 0 -> 3365 bytes
 .../ignite/visor/commands/kill/Packet.scala     |   78 +
 .../visor/commands/kill/VisorKillCommand.scala  |  357 +++
 .../ignite/visor/commands/node/Packet.scala     |   60 +
 .../visor/commands/node/VisorNodeCommand.scala  |  343 +++
 .../ignite/visor/commands/ping/Packet.scala     |   50 +
 .../visor/commands/ping/VisorPingCommand.scala  |  229 ++
 .../ignite/visor/commands/start/Packet.scala    |   90 +
 .../commands/start/VisorStartCommand.scala      |  430 +++
 .../ignite/visor/commands/tasks/Packet.scala    |  116 +
 .../commands/tasks/VisorTasksCommand.scala      | 1489 ++++++++++
 .../ignite/visor/commands/top/Packet.scala      |   87 +
 .../commands/top/VisorTopologyCommand.scala     |  433 +++
 .../ignite/visor/commands/vvm/Packet.scala      |   61 +
 .../visor/commands/vvm/VisorVvmCommand.scala    |  309 ++
 .../scala/org/apache/ignite/visor/visor.scala   | 2676 +++++++++++++++++
 .../main/scala/org/gridgain/visor/Packet.scala  |   38 -
 .../gridgain/visor/commands/VisorConsole.scala  |  344 ---
 .../visor/commands/VisorConsoleCommand.scala    |   78 -
 .../visor/commands/VisorTextTable.scala         |  539 ----
 .../gridgain/visor/commands/ack/Packet.scala    |   55 -
 .../visor/commands/ack/VisorAckCommand.scala    |  162 --
 .../gridgain/visor/commands/alert/Packet.scala  |  108 -
 .../commands/alert/VisorAlertCommand.scala      |  839 ------
 .../gridgain/visor/commands/cache/Packet.scala  |  127 -
 .../commands/cache/VisorCacheClearCommand.scala |  150 -
 .../commands/cache/VisorCacheCommand.scala      |  893 ------
 .../cache/VisorCacheCompactCommand.scala        |  150 -
 .../commands/cache/VisorCacheScanCommand.scala  |  236 --
 .../commands/cache/VisorCacheSwapCommand.scala  |  150 -
 .../gridgain/visor/commands/config/Packet.scala |   55 -
 .../config/VisorConfigurationCommand.scala      |  501 ----
 .../gridgain/visor/commands/deploy/Packet.scala |   76 -
 .../commands/deploy/VisorDeployCommand.scala    |  612 ----
 .../gridgain/visor/commands/disco/Packet.scala  |   72 -
 .../commands/disco/VisorDiscoveryCommand.scala  |  306 --
 .../gridgain/visor/commands/events/Packet.scala |   97 -
 .../commands/events/VisorEventsCommand.scala    |  530 ----
 .../org/gridgain/visor/commands/gc/Packet.scala |   61 -
 .../visor/commands/gc/VisorGcCommand.scala      |  244 --
 .../gridgain/visor/commands/ggcube_128x128.png  |  Bin 11444 -> 0 bytes
 .../gridgain/visor/commands/ggcube_48x48.png    |  Bin 3365 -> 0 bytes
 .../gridgain/visor/commands/kill/Packet.scala   |   78 -
 .../visor/commands/kill/VisorKillCommand.scala  |  356 ---
 .../gridgain/visor/commands/node/Packet.scala   |   60 -
 .../visor/commands/node/VisorNodeCommand.scala  |  342 ---
 .../gridgain/visor/commands/ping/Packet.scala   |   50 -
 .../visor/commands/ping/VisorPingCommand.scala  |  228 --
 .../gridgain/visor/commands/start/Packet.scala  |   90 -
 .../commands/start/VisorStartCommand.scala      |  429 ---
 .../gridgain/visor/commands/tasks/Packet.scala  |  116 -
 .../commands/tasks/VisorTasksCommand.scala      | 1488 ----------
 .../gridgain/visor/commands/top/Packet.scala    |   87 -
 .../commands/top/VisorTopologyCommand.scala     |  432 ---
 .../gridgain/visor/commands/vvm/Packet.scala    |   61 -
 .../visor/commands/vvm/VisorVvmCommand.scala    |  308 --
 .../main/scala/org/gridgain/visor/visor.scala   | 2679 ------------------
 .../ignite/visor/VisorRuntimeBaseSpec.scala     |   75 +
 .../ignite/visor/VisorTextTableSpec.scala       |   44 +
 .../visor/commands/VisorArgListSpec.scala       |   72 +
 .../commands/VisorFileNameCompleterSpec.scala   |   58 +
 .../commands/ack/VisorAckCommandSpec.scala      |   41 +
 .../commands/alert/VisorAlertCommandSpec.scala  |  155 +
 .../cache/VisorCacheClearCommandSpec.scala      |  116 +
 .../commands/cache/VisorCacheCommandSpec.scala  |  103 +
 .../cache/VisorCacheCompactCommandSpec.scala    |  106 +
 .../config/VisorConfigurationCommandSpec.scala  |   52 +
 .../cswap/VisorCacheSwapCommandSpec.scala       |   92 +
 .../deploy/VisorDeployCommandSpec.scala         |   35 +
 .../disco/VisorDiscoveryCommandSpec.scala       |   87 +
 .../events/VisorEventsCommandSpec.scala         |   64 +
 .../visor/commands/gc/VisorGcCommandSpec.scala  |   58 +
 .../commands/help/VisorHelpCommandSpec.scala    |   70 +
 .../commands/kill/VisorKillCommandSpec.scala    |   59 +
 .../commands/log/VisorLogCommandSpec.scala      |   34 +
 .../commands/mem/VisorMemoryCommandSpec.scala   |   77 +
 .../commands/node/VisorNodeCommandSpec.scala    |   43 +
 .../commands/open/VisorOpenCommandSpec.scala    |   42 +
 .../commands/ping/VisorPingCommandSpec.scala    |   39 +
 .../commands/start/VisorStartCommandSpec.scala  |  124 +
 .../commands/tasks/VisorTasksCommandSpec.scala  |  232 ++
 .../commands/top/VisorTopologyCommandSpec.scala |   63 +
 .../commands/vvm/VisorVvmCommandSpec.scala      |   47 +
 .../testsuites/VisorConsoleSelfTestSuite.scala  |   95 +
 .../gridgain/visor/VisorRuntimeBaseSpec.scala   |   75 -
 .../org/gridgain/visor/VisorTextTableSpec.scala |   45 -
 .../visor/commands/VisorArgListSpec.scala       |   71 -
 .../commands/VisorFileNameCompleterSpec.scala   |   58 -
 .../commands/ack/VisorAckCommandSpec.scala      |   40 -
 .../commands/alert/VisorAlertCommandSpec.scala  |  154 -
 .../cache/VisorCacheClearCommandSpec.scala      |  115 -
 .../commands/cache/VisorCacheCommandSpec.scala  |  102 -
 .../cache/VisorCacheCompactCommandSpec.scala    |  105 -
 .../config/VisorConfigurationCommandSpec.scala  |   51 -
 .../cswap/VisorCacheSwapCommandSpec.scala       |   91 -
 .../deploy/VisorDeployCommandSpec.scala         |   34 -
 .../disco/VisorDiscoveryCommandSpec.scala       |   86 -
 .../events/VisorEventsCommandSpec.scala         |   63 -
 .../visor/commands/gc/VisorGcCommandSpec.scala  |   57 -
 .../commands/help/VisorHelpCommandSpec.scala    |   69 -
 .../commands/kill/VisorKillCommandSpec.scala    |   58 -
 .../commands/log/VisorLogCommandSpec.scala      |   33 -
 .../commands/mem/VisorMemoryCommandSpec.scala   |   76 -
 .../commands/node/VisorNodeCommandSpec.scala    |   42 -
 .../commands/open/VisorOpenCommandSpec.scala    |   41 -
 .../commands/ping/VisorPingCommandSpec.scala    |   38 -
 .../commands/start/VisorStartCommandSpec.scala  |  123 -
 .../commands/tasks/VisorTasksCommandSpec.scala  |  231 --
 .../commands/top/VisorTopologyCommandSpec.scala |   62 -
 .../commands/vvm/VisorVvmCommandSpec.scala      |   46 -
 .../testsuites/VisorConsoleSelfTestSuite.scala  |   97 -
 137 files changed, 15327 insertions(+), 15292 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/e1c3c8ce/bin/ggvisorcmd.sh
----------------------------------------------------------------------
diff --git a/bin/ggvisorcmd.sh b/bin/ggvisorcmd.sh
index b84f002..6fe6c55 100755
--- a/bin/ggvisorcmd.sh
+++ b/bin/ggvisorcmd.sh
@@ -108,13 +108,13 @@ case $osname in
         "$JAVA" ${JVM_OPTS} ${QUIET} "${DOCK_OPTS}" \
         -DGRIDGAIN_UPDATE_NOTIFIER=false -DGRIDGAIN_HOME="${GRIDGAIN_HOME}" 
-DGRIDGAIN_PROG_NAME="$0" \
         -DGRIDGAIN_DEPLOYMENT_MODE_OVERRIDE=ISOLATED ${JVM_XOPTS} -cp "${CP}" \
-        org.gridgain.visor.commands.VisorConsole
+        org.apache.ignite.visor.commands.VisorConsole
     ;;
     *)
         "$JAVA" ${JVM_OPTS} ${QUIET} -DGRIDGAIN_UPDATE_NOTIFIER=false \
         -DGRIDGAIN_HOME="${GRIDGAIN_HOME}" -DGRIDGAIN_PROG_NAME="$0" 
-DGRIDGAIN_DEPLOYMENT_MODE_OVERRIDE=ISOLATED \
         ${JVM_XOPTS} -cp "${CP}" \
-        org.gridgain.visor.commands.VisorConsole
+        org.apache.ignite.visor.commands.VisorConsole
     ;;
 esac
 

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/e1c3c8ce/modules/visor-console/src/main/scala/org/apache/ignite/visor/Packet.scala
----------------------------------------------------------------------
diff --git 
a/modules/visor-console/src/main/scala/org/apache/ignite/visor/Packet.scala 
b/modules/visor-console/src/main/scala/org/apache/ignite/visor/Packet.scala
new file mode 100644
index 0000000..d3e2e1e
--- /dev/null
+++ b/modules/visor-console/src/main/scala/org/apache/ignite/visor/Packet.scala
@@ -0,0 +1,38 @@
+/*
+ * 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
+
+/**
+ * {{{
+ * ___    _________________________ ________
+ * __ |  / /____  _/__  ___/__  __ \___  __ \
+ * __ | / /  __  /  _____ \ _  / / /__  /_/ /
+ * __ |/ /  __/ /   ____/ / / /_/ / _  _, _/
+ * _____/   /___/   /____/  \____/  /_/ |_|
+ *
+ * }}}
+ *
+ * ==Overview==
+ * Visor console provides monitoring capabilities for GridGain.
+ *
+ * ==Usage==
+ * GridGain ships with `GRIDGAIN_HOME/bin/ggvisorcmd.{sh|bat}` script that 
starts Visor console.
+ *
+ * Just type:<ex>help</ex> in Visor console to get help and get started.
+ */
+package object visor

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/e1c3c8ce/modules/visor-console/src/main/scala/org/apache/ignite/visor/commands/VisorConsole.scala
----------------------------------------------------------------------
diff --git 
a/modules/visor-console/src/main/scala/org/apache/ignite/visor/commands/VisorConsole.scala
 
b/modules/visor-console/src/main/scala/org/apache/ignite/visor/commands/VisorConsole.scala
new file mode 100644
index 0000000..4f74794
--- /dev/null
+++ 
b/modules/visor-console/src/main/scala/org/apache/ignite/visor/commands/VisorConsole.scala
@@ -0,0 +1,344 @@
+/*
+ * 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.visor.commands
+
+import org.apache.ignite.internal.GridProductImpl
+import org.apache.ignite.internal.util.GridUtils
+import org.apache.ignite.internal.util.typedef.internal.U
+import org.apache.ignite.internal.util.scala.impl
+
+import org.apache.ignite.startup.cmdline.AboutDialog
+
+import javax.swing.ImageIcon
+import java.awt.Image
+import java.io.File
+import java.text.SimpleDateFormat
+import java.util
+
+import org.apache.ignite.visor.visor
+
+import scala.tools.jline.console.ConsoleReader
+import scala.tools.jline.console.completer.Completer
+import scala.tools.jline.internal.Configuration
+
+// Built-in commands.
+// Note the importing of implicit conversions.
+import org.apache.ignite.visor.commands.ack.VisorAckCommand
+import org.apache.ignite.visor.commands.alert.VisorAlertCommand
+import org.apache.ignite.visor.commands.cache.{VisorCacheClearCommand, 
VisorCacheCommand, VisorCacheCompactCommand, VisorCacheSwapCommand}
+import org.apache.ignite.visor.commands.config.VisorConfigurationCommand
+import org.apache.ignite.visor.commands.deploy.VisorDeployCommand
+import org.apache.ignite.visor.commands.disco.VisorDiscoveryCommand
+import org.apache.ignite.visor.commands.events.VisorEventsCommand
+import org.apache.ignite.visor.commands.gc.VisorGcCommand
+import org.apache.ignite.visor.commands.kill.VisorKillCommand
+import org.apache.ignite.visor.commands.node.VisorNodeCommand
+import org.apache.ignite.visor.commands.ping.VisorPingCommand
+import org.apache.ignite.visor.commands.start.VisorStartCommand
+import org.apache.ignite.visor.commands.tasks.VisorTasksCommand
+import org.apache.ignite.visor.commands.top.VisorTopologyCommand
+import org.apache.ignite.visor.commands.vvm.VisorVvmCommand
+
+/**
+ * Command line Visor.
+ */
+object VisorConsole extends App {
+    /** Version number. */
+    private final val VISOR_VER = GridProductImpl.VER
+
+    /** Release date. */
+    private final val VISOR_RELEASE_DATE = GridProductImpl.RELEASE_DATE
+
+    /** Copyright. */
+    private final val VISOR_COPYRIGHT = GridProductImpl.COPYRIGHT
+
+    /** Release date (another format). */
+    private final val releaseDate = new 
SimpleDateFormat("ddMMyyyy").parse(VISOR_RELEASE_DATE)
+
+    // Pre-initialize built-in commands.
+    VisorAckCommand
+    VisorAlertCommand
+    VisorCacheCommand
+    VisorCacheClearCommand
+    VisorCacheCompactCommand
+    VisorCacheSwapCommand
+    VisorConfigurationCommand
+    VisorDeployCommand
+    VisorDiscoveryCommand
+    VisorEventsCommand
+    VisorGcCommand
+    VisorKillCommand
+    VisorNodeCommand
+    VisorPingCommand
+    VisorTopologyCommand
+    VisorStartCommand
+    VisorTasksCommand
+    VisorVvmCommand
+
+    // Setting up Mac OS specific system menu.
+    customizeUI()
+
+    // Wrap line symbol for user input.
+    private val wrapLine = if (GridUtils.isWindows) "^" else "\\"
+
+    private val emptyArg = "^([a-zA-z!?]+)$".r
+    private val varArg = "^([a-zA-z!?]+)\\s+(.+)$".r
+
+    private var line: String = null
+
+    private val buf = new StringBuilder
+
+    private val reader = new ConsoleReader()
+
+    reader.addCompleter(new VisorCommandCompleter(visor.commands))
+    reader.addCompleter(new VisorFileNameCompleter())
+
+    welcomeMessage()
+
+    private var ok = true
+
+    while (ok) {
+        line = reader.readLine("visor> ")
+
+        ok = line != null
+
+        if (ok) {
+            line = line.trim
+
+            if (line.endsWith(wrapLine)) {
+                buf.append(line.dropRight(1))
+            }
+            else {
+                if (buf.size != 0) {
+                    buf.append(line)
+
+                    line = buf.toString()
+
+                    buf.clear()
+                }
+
+                try {
+                    line match {
+                        case emptyArg(c) =>
+                            visor.searchCmd(c) match {
+                                case Some(cmdHolder) => cmdHolder.impl.invoke()
+                                case _ => adviseToHelp(c)
+                            }
+                        case varArg(c, args) =>
+                            visor.searchCmd(c) match {
+                                case Some(cmdHolder) => 
cmdHolder.impl.invoke(args.trim)
+                                case _ => adviseToHelp(c)
+                            }
+                        case s if "".equals(s.trim) => // Ignore empty user 
input.
+                        case _ => adviseToHelp(line)
+                    }
+                } catch {
+                    case ignore: Exception => ignore.printStackTrace()
+                }
+            }
+        }
+    }
+
+    def terminalWidth() = reader.getTerminal.getWidth
+
+    /**
+     * Prints standard 'Invalid command' error message.
+     */
+    private def adviseToHelp(input: String) {
+        visor.warn(
+            "Invalid command name: '" + input + "'",
+            "Type 'help' to print commands list."
+        )
+    }
+
+    /**
+     * Print banner, hint message on start.
+     */
+    private def welcomeMessage() {
+        visor.status()
+
+        println("\nType 'help' for more information.")
+        println("Type 'open' to join the grid.")
+        println("Type 'quit' to quit form Visor console.")
+
+        visor.nl()
+    }
+
+    /**
+     * Setting up mac os specific menu.
+     */
+    private def customizeUI() {
+        def urlIcon(iconPath: String) = {
+            val dockIconUrl = getClass.getResource(iconPath)
+
+            assert(dockIconUrl != null, "Unknown icon path: " + iconPath)
+
+            dockIconUrl
+        }
+
+        try {
+            val appCls = Class.forName("com.apple.eawt.Application")
+            val aboutHndCls = Class.forName("com.apple.eawt.AboutHandler")
+
+            val osxApp = 
appCls.getDeclaredMethod("getApplication").invoke(null)
+
+            val dockIco = new ImageIcon(urlIcon("ggcube_node_128x128.png"))
+
+            appCls.getDeclaredMethod("setDockIconImage", 
classOf[Image]).invoke(osxApp, dockIco.getImage)
+
+            val bannerIconUrl = urlIcon("ggcube_node_48x48.png")
+
+            val aboutHndProxy = java.lang.reflect.Proxy.newProxyInstance(
+                appCls.getClassLoader,
+                Array[Class[_]](aboutHndCls),
+                new java.lang.reflect.InvocationHandler {
+                    def invoke(proxy: Any, mth: java.lang.reflect.Method, 
args: Array[Object]) = {
+                        AboutDialog.centerShow("Visor - GridGain Shell 
Console", bannerIconUrl.toExternalForm,
+                            VISOR_VER, releaseDate, VISOR_COPYRIGHT)
+
+                        null
+                    }
+                })
+
+            appCls.getDeclaredMethod("setAboutHandler", 
aboutHndCls).invoke(osxApp, aboutHndProxy)
+        }
+        catch {
+            // Specifically ignore it here.
+            case _: Throwable =>
+        }
+    }
+}
+
+/**
+ * Visor command list completer.
+ *
+ * @param commands Commands list.
+ */
+private[commands] class VisorCommandCompleter(commands: Seq[String]) extends 
Completer {
+    import scala.collection.JavaConversions._
+
+    /** ordered commands. */
+    private final val strings = new util.TreeSet[String](commands)
+
+    @impl def complete(buf: String, cursor: Int, candidates: 
util.List[CharSequence]): Int = {
+        // buffer could be null
+        assert(candidates != null)
+
+        if (buf == null)
+            candidates.addAll(strings)
+        else
+            
strings.tailSet(buf).takeWhile(_.startsWith(buf)).foreach(candidates.add)
+
+        if (candidates.size == 1)
+            candidates.set(0, candidates.get(0) + " ")
+
+        if (candidates.isEmpty) -1 else 0
+    }
+}
+
+/**
+ * File path completer for different place of path in command.
+ */
+private[commands] class VisorFileNameCompleter extends Completer {
+    protected lazy val getUserHome = Configuration.getUserHome
+
+    protected lazy val separator = File.separator
+
+    @impl def complete(buf: String, cursor: Int, candidates: 
util.List[CharSequence]): Int = {
+        assert(candidates != null)
+
+        var ixBegin = 0
+
+        // extracted path from buffer.
+        val path = buf match {
+            case null => ""
+            case emptyStr if emptyStr.trim == "" => ""
+            case str =>
+                // replace wrong '/' on windows.
+                val translated = if (GridUtils.isWindows) str.replace('/', 
'\\') else str
+
+                // line before cursor.
+                val left = translated.substring(0, cursor)
+
+                // path begin marker.
+                val quote = if (left.count(_ == '\"') % 2 == 1) "\""
+                    else if (left.count(_ == '\'') % 2 == 1) "\'"
+                    else ""
+
+                val splitterSz = quote.size + " ".size
+
+                // path begin marker index.
+                ixBegin = left.lastIndexOf(" " + quote)
+                ixBegin = if (ixBegin != -1) ixBegin + splitterSz else 
left.length - 1
+
+                // path end marker index.
+                var ixEnd = translated.indexOf(quote + " ", cursor)
+                ixEnd = if (ixEnd != -1) ixEnd - splitterSz else 
translated.length
+
+                // extract path.
+                translated.substring(ixBegin, ixEnd)
+        }
+
+        // resolve path
+        val file = resolvePath(path)
+
+        // file dir and part of file name for complete.
+        val (dir, partOfName) = if (file.isDirectory) (file, "") else 
(file.getParentFile, file.getName)
+
+        // filter all files in directory by part of file name.
+        if (dir != null && dir.listFiles != null) {
+            val files = for (file <- dir.listFiles if 
file.getName.startsWith(partOfName)) yield file
+
+            if (files.size == 1) {
+                val candidate = files(0)
+
+                if (candidate.isDirectory) separator else " "
+
+                candidates.add(candidate.getName + (if (candidate.isDirectory) 
separator else " "))
+            }
+            else
+                files.foreach(f => candidates.add(f.getName))
+        }
+
+        if (candidates.size > 0) ixBegin + path.lastIndexOf(separator) + 
separator.length else -1
+    }
+
+    /**
+     * Gets File representing the path passed in. First the check is made if 
path is in user home directory.
+     * If not, then the check is if path is absolute.
+     * If all checks fail, then related to the current dir File is returned.
+     *
+     * @param path - Path to resolve.
+     * @return Resolved path as File
+     */
+    protected def resolvePath(path: String) = {
+        val homeDir = getUserHome
+
+        val absFile = new File(path)
+
+        // Special character: ~ maps to the user's home directory
+        if (path.startsWith("~" + separator))
+            new File(homeDir.getPath, path.substring(1))
+        else if (path.equals("~"))
+            homeDir.getParentFile
+        else if (absFile.exists() || absFile.getParentFile != null) // 
absolute path
+            absFile
+        else
+            new File(new File("").getAbsolutePath, path)
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/e1c3c8ce/modules/visor-console/src/main/scala/org/apache/ignite/visor/commands/VisorConsoleCommand.scala
----------------------------------------------------------------------
diff --git 
a/modules/visor-console/src/main/scala/org/apache/ignite/visor/commands/VisorConsoleCommand.scala
 
b/modules/visor-console/src/main/scala/org/apache/ignite/visor/commands/VisorConsoleCommand.scala
new file mode 100644
index 0000000..7192a87
--- /dev/null
+++ 
b/modules/visor-console/src/main/scala/org/apache/ignite/visor/commands/VisorConsoleCommand.scala
@@ -0,0 +1,77 @@
+/*
+ * 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.visor.commands
+
+import org.apache.ignite.internal.util.scala.impl
+import org.apache.ignite.visor.visor
+
+/**
+ * Command implementation.
+ */
+trait VisorConsoleCommand {
+    /**
+     * Command without arguments.
+     */
+    def invoke()
+
+    /**
+     * Command with arguments.
+     *
+     * @param args - arguments as string.
+     */
+    def invoke(args: String)
+}
+
+/**
+ * Singleton companion object.
+ */
+object VisorConsoleCommand {
+    /**
+     * Create `VisorConsoleCommand`.
+     *
+     * @param emptyArgs Function to execute in case of no args passed to 
command.
+     * @param withArgs Function to execute in case of some args passed to 
command.
+     * @return New instance of `VisorConsoleCommand`.
+     */
+    def apply(emptyArgs: () => Unit, withArgs: (String) => Unit) = {
+        new VisorConsoleCommand {
+            @impl def invoke() = emptyArgs.apply()
+
+            @impl def invoke(args: String) = withArgs.apply(args)
+        }
+    }
+
+    /**
+     * Create `VisorConsoleCommand`.
+     *
+     * @param emptyArgs Function to execute in case of no args passed to 
command.
+     * @return New instance of `VisorConsoleCommand`.
+     */
+    def apply(emptyArgs: () => Unit) = {
+        new VisorConsoleCommand {
+            @impl def invoke() = emptyArgs.apply()
+
+            @impl def invoke(args: String) {
+                visor.warn(
+                    "Invalid arguments for command without arguments.",
+                    "Type 'help' to print commands list."
+                )
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/e1c3c8ce/modules/visor-console/src/main/scala/org/apache/ignite/visor/commands/VisorTextTable.scala
----------------------------------------------------------------------
diff --git 
a/modules/visor-console/src/main/scala/org/apache/ignite/visor/commands/VisorTextTable.scala
 
b/modules/visor-console/src/main/scala/org/apache/ignite/visor/commands/VisorTextTable.scala
new file mode 100644
index 0000000..5f02b42
--- /dev/null
+++ 
b/modules/visor-console/src/main/scala/org/apache/ignite/visor/commands/VisorTextTable.scala
@@ -0,0 +1,539 @@
+/*
+ * 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.visor.commands
+
+import org.apache.ignite.internal.util.GridStringBuilder
+import org.apache.ignite.visor.commands.VisorTextTable._
+
+import scala.collection.Traversable
+
+/**
+ * ==Overview==
+ * Provides `ASCII`-based table with minimal styling support.
+ */
+class VisorTextTable {
+    /**
+     * Cell style.
+     */
+    private sealed class Style(
+        var leftPad: Int = 1, // >= 0
+        var rightPad: Int = 1, // >= 0
+        var align: String = "center" // center, left, right
+    ) {
+        assert(leftPad >= 0)
+        assert(rightPad >= 0)
+        assert(align != null)
+
+        /**
+         * Gets overall padding (left + right).
+         */
+        def padding: Int =
+            leftPad + rightPad
+    }
+
+    /**
+     * Cell style.
+     */
+    private object Style {
+        /**
+         *
+         * @param sty Style.
+         */
+        def apply(sty: String): Style = {
+            assert(sty != null)
+
+            val cs = new Style
+
+            if (!sty.isEmpty) {
+                for (e <- sty.split(',')) {
+                    val a = e.split(":")
+
+                    assert(a.length == 2, "Invalid cell style: " + e.trim)
+
+                    val a0 = a(0).trim
+                    val a1 = a(1).trim
+
+                    a0 match {
+                        case "leftPad" => cs.leftPad = a1.toInt
+                        case "rightPad" => cs.rightPad = a1.toInt
+                        case "align" => cs.align = a1
+                        case _ => assert(false, "Invalid style: " + e.trim)
+                    }
+                }
+            }
+
+            cs
+        }
+    }
+
+    /**
+     * Cell holder.
+     */
+    private sealed case class Cell(style: Style, lines: Seq[String]) {
+        assert(style != null)
+        assert(lines != null)
+
+        /**
+         * Cell's calculated width including padding.
+         */
+        lazy val width =
+            if (height > 0)
+                style.padding + lines.max(Ordering.by[String, 
Int](_.length)).length
+            else
+                style.padding
+
+        /**
+         * Gets height of the cell.
+         */
+        def height: Int = lines.length
+    }
+
+    /**
+     * Margin holder.
+     */
+    private sealed case class Margin(
+        top: Int = 0,
+        right: Int = 0,
+        bottom: Int = 0,
+        left: Int = 0) {
+        assert(top >= 0)
+        assert(right >= 0)
+        assert(bottom >= 0)
+        assert(left >= 0)
+    }
+
+    /** */
+    private val NL = '\n'
+
+    /** Headers. */
+    private val hdr = collection.mutable.ArrayBuffer.empty[Cell]
+
+    /** Rows. */
+    private val rows = collection.mutable.ArrayBuffer.empty[Seq[Cell]]
+
+    /** Current row, if any. */
+    private var curRow: collection.mutable.ArrayBuffer[Cell] = null
+
+    /** Table's margin, if any. */
+    private var margin: Margin = Margin()
+
+    /** Default row cell style, if any. */
+    private var rowSty: String = "align:left"
+
+    /** Default header cell style, if any. */
+    private var hdrSty: String = "align:center"
+
+    /**
+     * Flag indicating whether or not to draw inside horizontal lines
+     * between individual rows.
+     */
+    var insideBorder = false
+
+    /**
+     * Flag indicating whether of not to automatically draw horizontal lines
+     * for multiline rows.
+     */
+    var autoBorder = true
+
+    /**
+     * Maximum width of the cell. If any line in the cell exceeds this width
+     * it will be cut in two or more lines.
+     *
+     * '''NOTE''': it doesn't include into account the padding. Only the actual
+     * string length is counted.
+     */
+    var maxCellWidth = Int.MaxValue
+
+    /**
+     *
+     * @param ch Char.
+     * @param len Dash length.
+     */
+    private def dash(ch: Char, len: Int): String = {
+        assert(len >= 0)
+
+        new String().padTo(len, ch)
+    }
+
+    /**
+     *
+     * @param s String.
+     * @param len Dash length.
+     */
+    private def dash(s: String, len: Int): String = {
+        assert(len >= 0)
+
+        var i = 0
+
+        val sb = new GridStringBuilder(s.length * len)
+
+        while (i < len) {
+            sb.a(s)
+
+            i += 1
+        }
+
+        sb.toString
+    }
+
+    /**
+     *
+     * @param len Length.
+     */
+    private def blank(len: Int): String =
+        dash(' ', len)
+
+    /**
+     * Sets table's margin.
+     *
+     * @param top Top margin.
+     * @param right Right margin.
+     * @param bottom Bottom margin.
+     * @param left Left margin.
+     */
+    def margin(top: Int = 0, right: Int = 0, bottom: Int = 0, left: Int = 0) {
+        assert(top >= 0)
+        assert(right >= 0)
+        assert(bottom >= 0)
+        assert(left >= 0)
+
+        margin = Margin(top, right, bottom, left)
+    }
+
+    /**
+     * Starts data row.
+     */
+    def startRow() {
+        assert(curRow == null)
+
+        curRow = collection.mutable.ArrayBuffer.empty[Cell]
+    }
+
+    /**
+     * Ends data row.
+     */
+    def endRow() {
+        assert(curRow.nonEmpty)
+
+        rows += curRow
+
+        curRow = null
+    }
+
+    /**
+     * Adds row (one or more row cells).
+     *
+     * @param cells Row cells. For multi-line cells - use `Seq(...)`.
+     */
+    def +=(cells: Any*): VisorTextTable = {
+        startRow()
+
+        cells foreach {
+            case s: scala.collection.Iterable[Any] => addRowCell(s.toSeq: _*)
+            case p: Product => addRowCell(p.productIterator.toSeq: _*)
+            case a => addRowCell(a)
+        }
+
+        endRow()
+
+        this
+    }
+
+    /**
+     * Adds header (one or more header cells).
+     *
+     * @param cells Header cells. For multi-line cells - use `Seq(...)`.
+     */
+    def #=(cells: Any*): VisorTextTable = {
+        cells foreach {
+            case s: scala.collection.Iterable[Any] => addHeaderCell(s.toSeq: 
_*)
+            case p: Product => addHeaderCell(p.productIterator.toSeq: _*)
+            case a => addHeaderCell(a)
+        }
+
+        this
+    }
+
+    /**
+     * Adds single header cell.
+     *
+     * @param lines One or more cell lines.
+     */
+    def addHeaderCell(lines: Any*): VisorTextTable = {
+        assert(lines != null)
+        assert(lines.length > 0)
+
+        // Break up long line into multiple ones - if necessary.
+        val lst = lines flatten(_.toString.grouped(maxCellWidth))
+
+        hdr += Cell(Style(hdrSty), lst)
+
+        this
+    }
+
+    /**
+     * Gets current row style.
+     */
+    def rowStyle =
+        rowSty
+
+    /**
+     * Sets current row style.
+     *
+     * @param rowSty Row style to set.
+     */
+    def rowStyle(rowSty: String) {
+        this.rowSty = rowSty
+    }
+
+    /**
+     * Gets current header style.
+     */
+    def headerStyle =
+        rowSty
+
+    /**
+     * Sets current header style.
+     *
+     * @param hdrSty Header style to set.
+     */
+    def headerStyle(hdrSty: String) {
+        this.hdrSty = hdrSty
+    }
+
+    /**
+     * Adds single row cell.
+     *
+     * @param lines One or more row cells. Multiple lines will be printed on 
separate lines.
+     */
+    def addRowCell(lines: Any*): VisorTextTable = {
+        assert(lines != null)
+        assert(lines.length >= 0)
+        assert(curRow != null)
+
+        // Break up long line into multiple ones - if necessary.
+        val lst = lines flatten {
+            case it: Traversable[_] => 
it.flatten(_.toString.grouped(maxCellWidth))
+            case null => Seq("")
+            case obj => obj.toString.grouped(maxCellWidth)
+        }
+
+        curRow += Cell(Style(rowSty), lst)
+
+        this
+    }
+
+    /**
+     *
+     * @param txt Text to align.
+     * @param width Width already accounts for padding.
+     * @param sty Style.
+     */
+    private def aligned(txt: String, width: Int, sty: Style): String = {
+        assert(txt != null)
+        assert(width > 0)
+        assert(sty != null)
+        assert(txt.length <= width)
+
+        val d = width - txt.length
+
+        val styTxt = txt
+
+        sty.align.trim match {
+            case "center" =>
+                blank(d / 2) + styTxt + blank(d / 2 + d % 2)
+            case "left" =>
+                blank(sty.leftPad) + styTxt + blank(d - sty.leftPad)
+            case "right" =>
+                blank(d - sty.rightPad) + styTxt + blank(sty.rightPad)
+            case _ =>
+                throw new AssertionError("Invalid align option in: " + sty)
+        }
+    }
+
+    /**
+     * Renders this table.
+     */
+    def render() {
+        // Make sure table is not empty.
+        if (hdr.isEmpty && rows.isEmpty)
+            return
+
+        var colsNum = -1
+
+        val isHdr = hdr.nonEmpty
+
+        if (isHdr)
+            colsNum = hdr.size
+
+        // Calc number of columns and make sure all rows are even.
+        for (r <- rows)
+            if (colsNum == -1)
+                colsNum = r.size
+            else if (colsNum != r.size)
+                assert(false, "Table with uneven rows.")
+
+        assert(colsNum > 0)
+
+        // At this point all rows in the table have the
+        // the same number of columns.
+
+        val colWs = new Array[Int](colsNum) // Column widths.
+        val rowHs = new Array[Int](rows.length) // Row heights.
+
+        // Header height.
+        var hdrH = 0
+
+        // Initialize column widths with header row (if any).
+        for (i <- 0 until hdr.size) {
+            val c = hdr(i)
+
+            colWs(i) = c.width
+
+            hdrH = math.max(hdrH, c.height)
+        }
+
+        // Calc row heights and column widths.
+        for (i <- 0 until rows.length; j <- 0 until colsNum) {
+            val c = rows(i)(j)
+
+            rowHs(i) = math.max(rowHs(i), c.height)
+            colWs(j) = math.max(colWs(j), c.width)
+        }
+
+        // Table width without the border.
+        val tblW = colWs.sum + colsNum - 1
+
+        val tbl = new GridStringBuilder()
+
+        // Top margin.
+        for (i <- 0 until margin.top)
+            tbl.a(" ").a(NL)
+
+        // Print header, if any.
+        if (isHdr) {
+            tbl.a(blank(margin.left)).a(HDR_CRS).a(dash(HDR_HOR, 
tblW)).a(HDR_CRS).a(blank(margin.right)).a(NL)
+
+            for (i <- 0 until hdrH) {
+                // Left margin and '|'.
+                tbl.a(blank(margin.left)).a(HDR_VER)
+
+                for (j <- 0 until hdr.size) {
+                    val c = hdr(j)
+
+                    if (i >= 0 && i < c.height)
+                        tbl.a(aligned(c.lines(i), colWs(j), c.style))
+                    else
+                        tbl.a(blank(colWs(j)))
+
+                    tbl.a(HDR_VER) // '|'
+                }
+
+                // Right margin.
+                tbl.a(blank(margin.right)).a(NL)
+            }
+
+            tbl.a(blank(margin.left)).a(HDR_CRS).a(dash(HDR_HOR, 
tblW)).a(HDR_CRS).a(blank(margin.right)).a(NL)
+        }
+        else
+            tbl.a(blank(margin.left)).a(ROW_CRS).a(dash(ROW_HOR, 
tblW)).a(ROW_CRS).a(blank(margin.right)).a(NL)
+
+        // Print rows, if any.
+        if (rows.nonEmpty) {
+            val horLine = (i: Int) => {
+                // Left margin and '+'
+                tbl.a(blank(margin.left)).a(ROW_CRS)
+
+                for (k <- 0 until rows(i).size)
+                    tbl.a(dash(ROW_HOR, colWs(k))).a(ROW_CRS)
+
+                // Right margin.
+                tbl.a(blank(margin.right)).a(NL)
+            }
+
+            for (i <- 0 until rows.size) {
+                val r = rows(i)
+
+                val rowH = rowHs(i)
+
+                if (i > 0 && ((rowH > 1 && autoBorder) || insideBorder) && 
rowHs(i - 1) == 1)
+                    horLine(i)
+
+                for (j <- 0 until rowH) {
+                    // Left margin and '|'
+                    tbl.a(blank(margin.left)).a(ROW_VER)
+
+                    for (k <- 0 until r.size) {
+                        val c = r(k)
+                        val w = colWs(k)
+
+                        if (j < c.height)
+                            tbl.a(aligned(c.lines(j), w, c.style))
+                        else
+                            tbl.a(blank(w))
+
+                        tbl.a(ROW_VER) // '|'
+                    }
+
+                    // Right margin.
+                    tbl.a(blank(margin.right)).a(NL)
+                }
+
+                if (i < rows.size - 1 && ((rowH > 1 && autoBorder) || 
insideBorder))
+                    horLine(i)
+            }
+
+            tbl.a(blank(margin.left)).a(ROW_CRS).a(dash(ROW_HOR, 
tblW)).a(ROW_CRS).a(blank(margin.right)).a(NL)
+        }
+
+        // Bottom margin.
+        for (i <- 1 to margin.bottom)
+            tbl.a(" ").a(NL)
+
+        print(tbl.toString)
+    }
+}
+
+/**
+ * Static context.
+ */
+object VisorTextTable {
+    /** Table header horizontal line. */
+    private val HDR_HOR = "="
+
+    /** Table header vertical line. */
+    private val HDR_VER = "|"
+
+    /** Table header crossroad line. */
+    private val HDR_CRS = "+"
+
+    /** Table row horizontal line. */
+    private val ROW_HOR = '-'
+
+    /** Table row vertical line. */
+    private val ROW_VER = '|'
+
+    /** Table row crossroad line. */
+    private val ROW_CRS = "+"
+
+    /**
+      * Creates new Visor text table.
+      */
+    def apply() =
+        new VisorTextTable
+}

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/e1c3c8ce/modules/visor-console/src/main/scala/org/apache/ignite/visor/commands/ack/Packet.scala
----------------------------------------------------------------------
diff --git 
a/modules/visor-console/src/main/scala/org/apache/ignite/visor/commands/ack/Packet.scala
 
b/modules/visor-console/src/main/scala/org/apache/ignite/visor/commands/ack/Packet.scala
new file mode 100644
index 0000000..1529d71
--- /dev/null
+++ 
b/modules/visor-console/src/main/scala/org/apache/ignite/visor/commands/ack/Packet.scala
@@ -0,0 +1,55 @@
+/*
+ * 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.visor.commands
+
+/**
+ * ==Overview==
+ * Visor 'ack' command implementation.
+ *
+ * ==Help==
+ * {{{
+ * +-------------------------------------------+
+ * | ack | Acks arguments on all remote nodes. |
+ * +-------------------------------------------+
+ * }}}
+ *
+ * ====Specification====
+ * {{{
+ *     ack {"s"}
+ *     ack ("s", f)
+ * }}}
+ *
+ * ====Arguments====
+ * {{{
+ *     s
+ *         Optional string to print on each remote node.
+ *     f
+ *         Optional Scala predicate on 'ScalarRichNodePimp' filtering nodes in 
the topology.
+ * }}}
+ *
+ * ====Examples====
+ * {{{
+ *     ack "Howdy!"
+ *         Prints 'Howdy!' on all nodes in the topology.
+ *     ack("Howdy!", _.id8.startsWith("123"))
+ *         Prints 'Howdy!' on all nodes satisfying this predicate.
+ *     ack
+ *         Prints local node ID on all nodes in the topology.
+ * }}}
+ */
+package object ack

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/e1c3c8ce/modules/visor-console/src/main/scala/org/apache/ignite/visor/commands/ack/VisorAckCommand.scala
----------------------------------------------------------------------
diff --git 
a/modules/visor-console/src/main/scala/org/apache/ignite/visor/commands/ack/VisorAckCommand.scala
 
b/modules/visor-console/src/main/scala/org/apache/ignite/visor/commands/ack/VisorAckCommand.scala
new file mode 100644
index 0000000..a1f3f25
--- /dev/null
+++ 
b/modules/visor-console/src/main/scala/org/apache/ignite/visor/commands/ack/VisorAckCommand.scala
@@ -0,0 +1,163 @@
+/*
+ * 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.visor.commands.ack
+
+import org.apache.ignite.internal.visor.misc.VisorAckTask
+
+import org.apache.ignite.cluster.ClusterGroupEmptyException
+
+import java.util.{HashSet => JavaHashSet}
+
+import org.apache.ignite.visor.{VisorTag, visor}
+import org.gridgain.visor._
+import org.apache.ignite.visor.commands.VisorConsoleCommand
+import visor._
+
+import scala.collection.JavaConversions._
+import scala.language.implicitConversions
+
+/**
+ * ==Overview==
+ * Visor 'ack' command implementation.
+ *
+ * ==Help==
+ * {{{
+ * +-------------------------------------------+
+ * | ack | Acks arguments on all remote nodes. |
+ * +-------------------------------------------+
+ * }}}
+ *
+ * ====Specification====
+ * {{{
+ *     ack {"s"}
+ *     ack ("s", f)
+ * }}}
+ *
+ * ====Arguments====
+ * {{{
+ *     s
+ *         Optional string to print on each remote node.
+ *     f
+ *         Optional Scala predicate on 'ScalarRichNodePimp' filtering nodes in 
the topology.
+ * }}}
+ *
+ * ====Examples====
+ * {{{
+ *     ack "Howdy!"
+ *         Prints 'Howdy!' on all nodes in the topology.
+ *     ack("Howdy!", _.id8.startsWith("123"))
+ *         Prints 'Howdy!' on all nodes satisfying this predicate.
+ *     ack
+ *         Prints local node ID on all nodes in the topology.
+ * }}}
+ */
+class VisorAckCommand {
+    /**
+     * Prints error message and advise.
+     *
+     * @param errMsgs Error messages.
+     */
+    private def scold(errMsgs: Any*) {
+        assert(errMsgs != null)
+
+        warn(errMsgs: _*)
+        warn("Type 'help ack' to see how to use this command.")
+    }
+
+    /**
+     * ===Command===
+     * Acks local node ID on all nodes. Note that this command
+     * behaves differently from its sibling that takes an argument.
+     *
+     * ===Example===
+     * <ex>ack</ex>
+     * Prints local node IDs on all nodes in the topology.
+     */
+    def ack() {
+        ack(null)
+    }
+
+    /**
+     * ===Command===
+     * Acks its argument on all nodes.
+     *
+     * ===Example===
+     * <ex>ack "Howdy!"</ex>
+     * prints 'Howdy!' on all nodes in the topology.
+     *
+     * @param msg Optional command argument. If `null` this function is no-op.
+     */
+    def ack(msg: String) {
+        if (!isConnected)
+            adviseToConnect()
+        else
+            try {
+                val nodeIds = grid.nodes().map(_.id())
+
+                grid.compute(grid.forNodeIds(nodeIds))
+                    .withName("visor-ack")
+                    .withNoFailover()
+                    .execute(classOf[VisorAckTask], toTaskArgument(nodeIds, 
msg))
+            }
+            catch {
+                case _: ClusterGroupEmptyException => scold("Topology is 
empty.")
+                case e: Exception => scold("System error: " + e.getMessage)
+            }
+    }
+}
+
+/**
+ * Companion object that does initialization of the command.
+ */
+object VisorAckCommand {
+    // Adds command's help to visor.
+    addHelp(
+        name = "ack",
+        shortInfo = "Acks arguments on all remote nodes.",
+        spec = Seq(
+            "ack",
+            "ack <message>"
+        ),
+        args = Seq(
+            "<message>" ->
+                "Optional string to print on each remote node."
+        ),
+        examples = Seq(
+            "ack" ->
+                "Prints local node ID on all nodes in the topology.",
+            "ack Howdy!" ->
+                "Prints 'Howdy!' on all nodes in the topology."
+        ),
+        ref = VisorConsoleCommand(cmd.ack, cmd.ack)
+    )
+
+    /** Singleton command. */
+    private val cmd = new VisorAckCommand
+
+    /**
+     * Singleton.
+     */
+    def apply() = cmd
+
+    /**
+     * Implicit converter from visor to commands "pimp".
+     *
+     * @param vs Visor tagging trait.
+     */
+    implicit def fromAck2Visor(vs: VisorTag) = cmd
+}

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/e1c3c8ce/modules/visor-console/src/main/scala/org/apache/ignite/visor/commands/alert/Packet.scala
----------------------------------------------------------------------
diff --git 
a/modules/visor-console/src/main/scala/org/apache/ignite/visor/commands/alert/Packet.scala
 
b/modules/visor-console/src/main/scala/org/apache/ignite/visor/commands/alert/Packet.scala
new file mode 100644
index 0000000..8322228
--- /dev/null
+++ 
b/modules/visor-console/src/main/scala/org/apache/ignite/visor/commands/alert/Packet.scala
@@ -0,0 +1,108 @@
+/*
+ * 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.visor.commands
+
+/**
+ * ==Overview==
+ * Visor 'alert' command implementation.
+ *
+ * ==Help==
+ * {{{
+ * +---------------------------------------------------------------------+
+ * | alert | Generates email alerts for user-defined events.             |
+ * |       | Node events and grid-wide events are defined via mnemonics. |
+ * +---------------------------------------------------------------------+
+ * }}}
+ *
+ * ====Specification====
+ * {{{
+ *     alert
+ *     alert "-u {-id=<alert-id>|-a}"
+ *     alert "-r {-t=<sec>} -c1=e1<num> -c2=e2<num> ... -ck=ek<num>"
+ * }}}
+ *
+ * ====Arguments====
+ * {{{
+ *     -u
+ *         Unregisters alert(s). Either '-a' flag or '-id' parameter is 
required.
+ *         Note that only one of the '-u' or '-r' is allowed.
+ *         If neither '-u' or '-r' provided - all alerts will be printed.
+ *     -a
+ *         When provided with '-u' - all alerts will be unregistered.
+ *     -id=<alert-id>
+ *         When provided with '-u' - alert with matching ID will be 
unregistered.
+ *     -r
+ *         Register new alert with mnemonic predicate(s).
+ *         Note that only one of the '-u' or '-r' is allowed.
+ *         If neither '-u' or '-r' provided - all alerts will be printed.
+ *     -t
+ *         Defines notification frequency in seconds. Default is 15 minutes.
+ *         This parameter can only appear with '-r'.
+ *     -ck=ek<num>
+ *         This defines a mnemonic for the metric that will be measured:
+ *         Grid-wide metrics (not node specific):
+ *            -cc Total number of available CPUs in the grid.
+ *            -nc Total number of nodes in the grid.
+ *            -hc Total number of physical hosts in the grid.
+ *            -cl Current average CPU load (in %) in the grid.
+ *
+ *         Per-node current metrics:
+ *            -aj Active jobs on the node.
+ *            -cj Cancelled jobs on the node.
+ *            -tc Thread count on the node.
+ *            -it Idle time on the node.
+ *                Note: <num> can have 's', 'm', or 'h' suffix indicating
+ *                seconds, minutes, and hours. By default (no suffix provided)
+ *                value is assumed to be in milliseconds.
+ *            -ut Up time on the node.
+ *                Note: <num> can have 's', 'm', or 'h' suffix indicating
+ *                seconds, minutes, and hours. By default (no suffix provided)
+ *                value is assumed to be in milliseconds.
+ *            -je Job execute time on the node.
+ *            -jw Job wait time on the node.
+ *            -wj Waiting jobs count on the node.
+ *            -rj Rejected jobs count on the node.
+ *            -hu Heap memory used (in MB) on the node.
+ *            -cd Current CPU load on the node.
+ *            -hm Heap memory maximum (in MB) on the node.
+ *
+ *         Comparison part of the mnemonic predicate:
+ *            =eq<num> Equal '=' to '<num>' number.
+ *            =neq<num> Not equal '!=' to '<num>' number.
+ *            =gt<num> Greater than '>' to '<num>' number.
+ *            =gte<num> Greater than or equal '>=' to '<num>' number.
+ *            =lt<num> Less than '<' to 'NN' number.
+ *            =lte<num> Less than or equal '<=' to '<num>' number.
+ *
+ *         NOTE: Email notification will be sent for the alert only when all
+ *               provided mnemonic predicates evaluate to 'true'.
+ * }}}
+ *
+ * ====Examples====
+ * {{{
+ *     alert
+ *         Prints all currently registered alerts.
+ *     alert "-u -a"
+ *         Unregisters all currently registered alerts.
+ *     alert "-u -id=12345678"
+ *         Unregisters alert with provided ID.
+ *     alert "-r -t=900 -cc=gte4 -cl=gt50"
+ *         Notify every 15 min if grid has >= 4 CPUs and > 50% CPU load.
+ * }}}
+ */
+package object alert

http://git-wip-us.apache.org/repos/asf/incubator-ignite/blob/e1c3c8ce/modules/visor-console/src/main/scala/org/apache/ignite/visor/commands/alert/VisorAlertCommand.scala
----------------------------------------------------------------------
diff --git 
a/modules/visor-console/src/main/scala/org/apache/ignite/visor/commands/alert/VisorAlertCommand.scala
 
b/modules/visor-console/src/main/scala/org/apache/ignite/visor/commands/alert/VisorAlertCommand.scala
new file mode 100644
index 0000000..69241fc
--- /dev/null
+++ 
b/modules/visor-console/src/main/scala/org/apache/ignite/visor/commands/alert/VisorAlertCommand.scala
@@ -0,0 +1,840 @@
+/*
+ * 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.visor.commands.alert
+
+import org.apache.ignite.internal.util.lang.{GridFunc => F}
+
+import org.apache.ignite._
+import org.apache.ignite.cluster.ClusterNode
+import org.apache.ignite.events.IgniteEventType._
+import org.apache.ignite.events.{IgniteDiscoveryEvent, IgniteEvent, 
IgniteEventType}
+import org.apache.ignite.lang.IgnitePredicate
+
+import java.util.UUID
+import java.util.concurrent.atomic._
+
+import org.apache.ignite.visor.VisorTag
+import org.gridgain.visor._
+import org.apache.ignite.visor.commands.{VisorConsoleCommand, VisorTextTable}
+import visor.visor._
+
+import scala.collection.immutable._
+import scala.language.implicitConversions
+import scala.util.control.Breaks._
+
+/**
+ * ==Overview==
+ * Visor 'alert' command implementation.
+ *
+ * ==Help==
+ * {{{
+ * +---------------------------------------------------------------------+
+ * | alert | Generates email alerts for user-defined events.             |
+ * |       | Node events and grid-wide events are defined via mnemonics. |
+ * +---------------------------------------------------------------------+
+ * }}}
+ *
+ * ====Specification====
+ * {{{
+ *     alert
+ *     alert "-u {-id=<alert-id>|-a}"
+ *     alert "-r {-t=<sec>} {-<metric>=<condition><value>} ... 
{-<metric>=<condition><value>}"
+ * }}}
+ *
+ * ====Arguments====
+ * {{{
+ *     -u
+ *         Unregisters alert(s). Either '-a' flag or '-id' parameter is 
required.
+ *         Note that only one of the '-u' or '-r' is allowed.
+ *         If neither '-u' or '-r' provided - all alerts will be printed.
+ *     -a
+ *         When provided with '-u' - all alerts will be unregistered.
+ *     -id=<alert-id>
+ *         When provided with '-u' - alert with matching ID will be 
unregistered.
+ *     -r
+ *         Register new alert with mnemonic predicate(s).
+ *         Note that only one of the '-u' or '-r' is allowed.
+ *         If neither '-u' or '-r' provided - all alerts will be printed.
+ *
+ *         NOTE: Email settings can be specified in GridGain configu
+ *         Email notification will be sent for the alert only
+ *         provided mnemonic predicates evaluate to 'true'."
+ *     -t
+ *         Defines notification frequency in seconds. Default is 15 minutes.
+ *         This parameter can only appear with '-r'.
+ *     -<metric>
+ *         This defines a mnemonic for the metric that will be measured:
+ *
+ *         Grid-wide metrics (not node specific):
+ *            cc - Total number of available CPUs in the grid.
+ *            nc - Total number of nodes in the grid.
+ *            hc - Total number of physical hosts in the grid.
+ *            cl - Current average CPU load (in %) in the grid.
+ *
+ *         Per-node current metrics:
+ *            aj - Active jobs on the node.
+ *            cj - Cancelled jobs on the node.
+ *            tc - Thread count on the node.
+ *            ut - Up time on the node.
+ *                Note: <num> can have 's', 'm', or 'h' suffix indicating
+ *                seconds, minutes, and hours. By default (no suffix provided)
+ *                value is assumed to be in milliseconds.
+ *            je - Job execute time on the node.
+ *            jw - Job wait time on the node.
+ *            wj - Waiting jobs count on the node.
+ *            rj - Rejected jobs count on the node.
+ *            hu - Heap memory used (in MB) on the node.
+ *            cd - Current CPU load on the node.
+ *            hm - Heap memory maximum (in MB) on the node.
+ *          ),
+ *     <condition>
+ *        Comparison part of the mnemonic predicate:
+ *           eq - Equal '=' to '<value>' number.
+ *           neq - Not equal '!=' to '<value>' number.
+ *           gt - Greater than '>' to '<value>' number.
+ *           gte - Greater than or equal '>=' to '<value>' number.
+ *           lt - Less than '<' to 'NN' number.
+ *           lte - Less than or equal '<=' to '<value>' number.
+ *
+ *         NOTE: Email notification will be sent for the alert only when all
+ *               provided mnemonic predicates evaluate to 'true'.
+ * }}}
+ *
+ * ====Examples====
+ * {{{
+ *     alert
+ *         Prints all currently registered alerts.
+ *     alert "-u -a"
+ *         Unregisters all currently registered alerts.
+ *     alert "-u -id=12345678"
+ *         Unregisters alert with provided ID.
+ *     alert "-r -t=900 -cc=gte4 -cl=gt50"
+ *         Notify every 15 min if grid has >= 4 CPUs and > 50% CPU load.
+ * }}}
+ */
+class VisorAlertCommand {
+    /** Default alert frequency. */
+    val DFLT_FREQ = 15L * 60L
+
+    /** Alerts. */
+    private var alerts = new HashMap[String, VisorAlert]
+
+    /** Map of last sent notification per alert ID. */
+    private var sent = new HashMap[(String, UUID), Long]
+
+    /** Map of alert statistics. */
+    private var stats = new HashMap[String, VisorStats]
+
+    /** Last 10 sent alerts. */
+    private var last10 = List.empty[VisorSentAlert]
+
+    /** Subscribe guard. */
+    private val guard = new AtomicBoolean(false)
+
+    /** Node metric update listener. */
+    private var lsnr: IgnitePredicate[IgniteEvent] = null
+
+    /**
+     * Prints error message and advise.
+     *
+     * @param errMsgs Error messages.
+     */
+    private def scold(errMsgs: Any*) {
+        assert(errMsgs != null)
+
+        warn(errMsgs: _*)
+        warn("Type 'help alert' to see how to use this command.")
+    }
+
+    /**
+     * ===Command===
+     * Lists all registered alerts.
+     *
+     * ===Examples===
+     * <ex>alert</ex>
+     * Prints all currently registered alerts.
+     */
+    def alert() {
+        alert("")
+    }
+
+    /**
+     * ===Command===
+     * Registers, unregisters and list alerts.
+     *
+     * ===Examples===
+     * <ex>alert "-u -a"</ex>
+     * Unregisters all currently registered alerts.
+     *
+     * <ex>alert "-i"</ex>
+     * Starts command in interactive mode.
+     *
+     * <ex>alert "-u -id=12345678"</ex>
+     * Unregisters alert with provided ID.
+     *
+     * <ex>alert "-r -t=900 -cc=gte4 -cl=gt50"</ex>
+     * Notify every 15 min if grid has >= 4 CPUs and > 50% CPU load.
+     *
+     * @param args Command arguments.
+     */
+    def alert(args: String) {
+        assert(args != null)
+
+        val argLst = parseArgs(args)
+
+        if (hasArgFlag("u", argLst))
+            unregisterAlert(argLst)
+        else if (hasArgFlag("r", argLst))
+            registerAlert(argLst)
+        else if (args.length() > 0)
+            scold("Invalid arguments: " + args)
+        else
+            printAlerts()
+    }
+
+    /**
+     * @param exprStr Expression string.
+     * @param f Node filter
+     * @param value Value generator.
+     */
+    private def makeNodeFilter(exprStr: String, f: ClusterNode => Boolean, 
value: ClusterNode => Long):
+        ClusterNode => Boolean = {
+        assert(exprStr != null)
+        assert(f != null)
+        assert(value != null)
+
+        val expr = makeExpression(exprStr)
+
+        // Note that if 'f(n)' is false - 'value' won't be evaluated.
+        if (expr.isDefined)
+            (n: ClusterNode) => f(n) && expr.get.apply(value(n))
+        else
+            throw new IgniteCheckedException("Invalid expression: " + exprStr)
+    }
+
+    /**
+     * @param exprStr Expression string.
+     * @param f Grid filter
+     * @param value Value generator.
+     */
+    private def makeGridFilter(exprStr: String, f: () => Boolean, value: () => 
Long): () => Boolean = {
+        assert(exprStr != null)
+        assert(f != null)
+        assert(value != null)
+
+        val expr = makeExpression(exprStr)
+
+        // Note that if 'f' is false - 'value' won't be evaluated.
+        if (expr.isDefined)
+            () => f() && expr.get.apply(value())
+        else
+            throw new IgniteCheckedException("Invalid expression: " + exprStr)
+    }
+
+    /**
+     * @param args Parsed argument list.
+     */
+    private def registerAlert(args: ArgList) {
+        breakable {
+            assert(args != null)
+
+            if (!isConnected)
+                adviseToConnect()
+            else {
+                // Warn but don't halt.
+                if (F.isEmpty(grid.configuration().getAdminEmails))
+                    warn("Admin emails are not configured (ignoring).")
+                else if (!grid.isSmtpEnabled)
+                    warn("SMTP is not configured (ignoring).")
+
+                val dfltNodeF = (_: ClusterNode) => true
+                val dfltGridF = () => true
+
+                var nf = dfltNodeF
+                var gf = dfltGridF
+
+                var freq = DFLT_FREQ
+
+                try {
+                    args foreach (arg => {
+                        val (n, v) = arg
+
+                        n match {
+                            // Grid-wide metrics (not node specific).
+                            case "cc" if v != null => gf = makeGridFilter(v, 
gf, grid.metrics().getTotalCpus)
+                            case "nc" if v != null => gf = makeGridFilter(v, 
gf, grid.metrics().getTotalNodes)
+                            case "hc" if v != null => gf = makeGridFilter(v, 
gf, grid.metrics().getTotalHosts)
+                            case "cl" if v != null => gf = makeGridFilter(v, 
gf,
+                                () => (grid.metrics().getAverageCpuLoad * 
100).toLong)
+
+                            // Per-node current metrics.
+                            case "aj" if v != null => nf = makeNodeFilter(v, 
nf, _.metrics().getCurrentActiveJobs)
+                            case "cj" if v != null => nf = makeNodeFilter(v, 
nf, _.metrics().getCurrentCancelledJobs)
+                            case "tc" if v != null => nf = makeNodeFilter(v, 
nf, _.metrics().getCurrentThreadCount)
+                            case "ut" if v != null => nf = makeNodeFilter(v, 
nf, _.metrics().getUpTime)
+                            case "je" if v != null => nf = makeNodeFilter(v, 
nf, _.metrics().getCurrentJobExecuteTime)
+                            case "jw" if v != null => nf = makeNodeFilter(v, 
nf, _.metrics().getCurrentJobWaitTime)
+                            case "wj" if v != null => nf = makeNodeFilter(v, 
nf, _.metrics().getCurrentWaitingJobs)
+                            case "rj" if v != null => nf = makeNodeFilter(v, 
nf, _.metrics().getCurrentRejectedJobs)
+                            case "hu" if v != null => nf = makeNodeFilter(v, 
nf, _.metrics().getHeapMemoryUsed)
+                            case "hm" if v != null => nf = makeNodeFilter(v, 
nf, _.metrics().getHeapMemoryMaximum)
+                            case "cd" if v != null => nf = makeNodeFilter(v, 
nf,
+                                (n: ClusterNode) => 
(n.metrics().getCurrentCpuLoad * 100).toLong)
+
+                            // Other tags.
+                            case "t" if v != null => freq = v.toLong
+                            case "r" => () // Skipping.
+                            case _ => throw new 
IgniteCheckedException("Invalid argument: " + makeArg(arg))
+                        }
+                    })
+                }
+                catch {
+                    case e: NumberFormatException =>
+                        scold("Number conversion error: " + e.getMessage)
+
+                        break()
+
+                    case e: Exception =>
+                        scold(e.getMessage)
+
+                        break()
+                }
+
+                if (nf == null && gf == null) {
+                    scold("No predicates have been provided in args: " + 
makeArgs(args))
+
+                    break()
+                }
+
+                val alert = new VisorAlert(
+                    id = id8,
+                    nodeFilter = nf,
+                    gridFilter = gf,
+                    perNode = nf != dfltNodeF,
+                    perGrid = gf != dfltGridF,
+                    spec = makeArgs(args),
+                    freq = freq,
+                    createdOn = System.currentTimeMillis(),
+                    varName = setVar(id8, "a")
+                )
+
+                // Subscribe for node metric updates - if needed.
+                registerListener()
+
+                alerts = alerts + (alert.id -> alert)
+                stats = stats + (alert.id -> VisorStats())
+
+                // Set visor var pointing to created alert.
+                mset(alert.varName, alert.id)
+
+                println("Alert '" + alert.id + "' (" + alert.varName + ") 
registered.")
+            }
+        }
+    }
+
+    /**
+     * Registers node metrics update listener, if one wasn't registered 
already.
+     */
+    private def registerListener() {
+        if (guard.compareAndSet(false, true)) {
+            assert(lsnr == null)
+
+            lsnr = new IgnitePredicate[IgniteEvent] {
+                override def apply(evt: IgniteEvent): Boolean = {
+                    val discoEvt = evt.asInstanceOf[IgniteDiscoveryEvent]
+
+                    val node = grid.node(discoEvt.eventNode().id())
+
+                    if (node != null)
+                        alerts foreach (t => {
+                            val (id, alert) = t
+
+                            var nb = false
+                            var gb = false
+
+                            try {
+                                nb = alert.nodeFilter(node)
+                                gb = alert.gridFilter()
+                            }
+                            catch {
+                                // In case of exception (like an empty 
projection) - simply return.
+                                case _: Throwable => return true
+                            }
+
+                            if (nb && gb) {
+                                val now = System.currentTimeMillis()
+
+                                val nKey = id -> node.id
+                                val gKey = id -> null
+
+                                var go: Boolean = false
+
+                                if (nb && alert.perNode)
+                                    go = (now - sent.getOrElse(nKey, 0L)) / 
1000 >= alert.freq
+
+                                if (!go && gb && alert.perGrid)
+                                    go = (now - sent.getOrElse(gKey, 0L)) / 
1000 >= alert.freq
+
+                                if (go) {
+                                    // Update throttling.
+                                    if (nb && alert.perNode)
+                                        sent = sent + (nKey -> now)
+
+                                    if (gb && alert.perGrid)
+                                        sent = sent + (gKey -> now)
+
+                                    val stat: VisorStats = stats(id)
+
+                                    assert(stat != null)
+
+                                    // Update stats.
+                                    if (stat.firstSnd == 0)
+                                        stat.firstSnd = now
+
+                                    stat.cnt += 1
+                                    stat.lastSnd = now
+
+                                    stats = stats + (id -> stat)
+
+                                    // Send alert email notification.
+                                    sendEmail(alert, if (nb) Some(node) else 
None)
+
+                                    // Write to Visor log if it is started 
(see 'log' command).
+                                    logText(
+                                        "Alert [" +
+                                            "id=" + alert.id + "(@" + 
alert.varName + "), " +
+                                            "spec=" + alert.spec + ", " +
+                                            "created on=" + 
formatDateTime(alert.createdOn) +
+                                        "]"
+                                    )
+
+                                    last10 = VisorSentAlert(
+                                        id = alert.id,
+                                        spec = alert.spec,
+                                        createdOn = alert.createdOn,
+                                        sentTs = now
+                                    ) +: last10
+
+                                    if (last10.size > 10)
+                                        last10 = last10.take(10)
+                                }
+                            }
+                        })
+
+                    true
+                }
+            }
+
+            grid.events().localListen(lsnr, EVT_NODE_METRICS_UPDATED)
+        }
+    }
+
+    /**
+     * Unregisters previously registered node metric update listener.
+     */
+    private def unregisterListener() {
+        if (guard.compareAndSet(true, false)) {
+            assert(lsnr != null)
+
+            assert(grid.events().stopLocalListen(lsnr))
+
+            lsnr = null
+        }
+    }
+
+    /**
+     * Resets command.
+     */
+    private def reset() {
+        unregisterAll()
+        unregisterListener()
+    }
+
+    /**
+     * Sends email.
+     *
+     * @param a Alert to send email about.
+     * @param n `Option` for node.
+     */
+    private def sendEmail(a: VisorAlert, n: Option[ClusterNode]) {
+        assert(a != null)
+        assert(n != null)
+
+        val subj = "Visor alert triggered: '" + a.spec + '\''
+        val headline = "GridGain ver. " + grid.product().version()
+
+        val stat = stats(a.id)
+
+        assert(stat != null)
+
+        var body =
+            headline + NL +
+            NL +
+            "----" + NL +
+            "Alert ID: " + a.id + NL +
+            "Alert spec: "  +  a.spec + NL
+
+        if (n.isDefined)
+            body += "Related node ID: " + n.get.id + NL +
+                "Related node addresses: " + n.get.addresses() + NL
+
+        body +=
+            "Send count: " + stat.cnt + NL +
+            "Created on: "  +  formatDateTime(a.createdOn) + NL +
+            "First send: " + (if (stat.firstSnd == 0) "n/a" else 
formatDateTime(stat.firstSnd)) + NL +
+            "Last send: " + (if (stat.lastSnd == 0) "n/a" else 
formatDateTime(stat.lastSnd)) + NL +
+            "----" + NL +
+            "Grid name: " + grid.name + NL
+
+        body +=
+            "----" + NL +
+            NL +
+            "NOTE:" + NL +
+            "This message is sent by Visor automatically to all configured 
admin emails." + NL +
+            "To change this behavior use 'adminEmails' grid configuration 
property." + NL +
+            NL +
+            "| www.gridgain.com" + NL +
+            "| supp...@gridgain.com" + NL
+
+        // Schedule sending.
+        grid.sendAdminEmailAsync(subj, body, false)
+    }
+
+    /**
+     * Prints advise.
+     */
+    private def advise() {
+        println("\nType 'help alert' to see how to manage alerts.")
+    }
+
+    /**
+     * Unregisters all alerts.
+     */
+    private def unregisterAll() {
+        mclear("-al")
+
+        alerts = new HashMap[String, VisorAlert]
+    }
+
+    /**
+     *
+     * @param args Parsed argument list.
+     */
+    private def unregisterAlert(args: ArgList) {
+        breakable {
+            assert(args != null)
+
+            if (alerts.isEmpty) {
+                scold("No alerts have been registered yet.")
+
+                break()
+            }
+
+            // Unregister all alerts.
+            if (hasArgFlag("a", args)) {
+                unregisterAll()
+
+                println("All alerts have been unregistered.")
+            }
+            // Unregister specific alert.
+            else if (hasArgName("id", args)) {
+                val idOpt = argValue("id", args)
+
+                if (idOpt.isDefined) {
+                    val id = idOpt.get
+
+                    val a = alerts.get(id)
+
+                    if (a.isDefined) {
+                        alerts -= id
+
+                        // Clear variable host.
+                        mclear(a.get.varName)
+
+                        println("Alert '" + id + "' unregistered.")
+                    }
+                    else {
+                        scold("Failed to find alert with ID: " + id)
+
+                        break()
+                    }
+                }
+                else {
+                    scold("No value for '-id' parameter found.")
+
+                    break()
+                }
+            }
+            else {
+                scold("Failed to unregister alert.", "Either \"-a\" or \"-id\" 
parameter is required.")
+
+                break()
+            }
+
+            if (alerts.isEmpty)
+                unregisterListener()
+        }
+    }
+
+    /**
+     * Prints out all alerts.
+     */
+    private def printAlerts() {
+        if (alerts.isEmpty)
+            println("No alerts are registered.")
+        else {
+            println("Summary:")
+
+            val sum = new VisorTextTable()
+
+            val firstSnd = (-1L /: stats.values)((b, a) => if (b == -1) 
a.firstSnd else math.min(b, a.firstSnd))
+            val lastSnd = (0L /: stats.values)((b, a) => math.max(b, 
a.lastSnd))
+
+            sum += ("Total alerts", alerts.size)
+            sum += ("Total sends", (0 /: stats.values)((b, a) => b + a.cnt))
+            sum += ("First send", if (firstSnd == 0) "n/a" else 
formatDateTime(firstSnd))
+            sum += ("Last send", if (lastSnd == 0) "n/a" else 
formatDateTime(lastSnd))
+
+            sum.render()
+        }
+
+        if (last10.isEmpty)
+            println("\nNo alerts have been sent.")
+        else {
+            val last10T = VisorTextTable()
+
+            last10T #= ("ID(@)", "Spec", "Sent", "Registered", "Count")
+
+            last10.foreach((a: VisorSentAlert) => last10T += (
+                a.idVar,
+                a.spec,
+                formatDateTime(a.sentTs),
+                formatDateTime(a.createdOn),
+                stats(a.id).cnt
+            ))
+
+            println("\nLast 10 Triggered Alerts:")
+
+            last10T.render()
+        }
+
+        if (alerts.nonEmpty) {
+            val tbl = new VisorTextTable()
+
+            tbl #= ("ID(@)", "Spec", "Count", "Registered", "First Send", 
"Last Send")
+
+            val sorted = alerts.values.toSeq.sortWith(_.varName < _.varName)
+
+            sorted foreach (a => {
+                val stat = stats(a.id)
+
+                tbl += (
+                    a.id + "(@" + a.varName + ')',
+                    a.spec,
+                    stat.cnt,
+                    formatDateTime(a.createdOn),
+                    if (stat.firstSnd == 0) "n/a" else 
formatDateTime(stat.firstSnd),
+                    if (stat.lastSnd == 0) "n/a" else 
formatDateTime(stat.lastSnd)
+                )
+            })
+
+            println("\nAlerts: " + sorted.size)
+
+            tbl.render()
+
+            // Print advise.
+            advise()
+        }
+    }
+
+    /**
+     * Gets unique ID8 id for the alert.
+     *
+     * @return 8-character locally unique alert ID.
+     */
+    private def id8: String = {
+        while (true) {
+            val id = UUID.randomUUID().toString.substring(0, 8)
+
+            // Have to check for guaranteed uniqueness.
+            if (!alerts.contains(id))
+                return id
+        }
+
+        assert(false, "Should never happen.")
+
+        ""
+    }
+}
+
+/**
+ * Visor alert.
+ */
+sealed private case class VisorAlert(
+    id: String,
+    nodeFilter: ClusterNode => Boolean,
+    gridFilter: () => Boolean,
+    freq: Long,
+    spec: String,
+    varName: String,
+    perNode: Boolean,
+    perGrid: Boolean,
+    createdOn: Long
+) {
+    assert(id != null)
+    assert(spec != null)
+    assert(varName != null)
+}
+
+/**
+ * Snapshot of the sent alert.
+ */
+private case class VisorSentAlert(
+    id: String,
+    sentTs: Long,
+    createdOn: Long,
+    spec: String
+) {
+    assert(id != null)
+    assert(spec != null)
+
+    def idVar: String = {
+        val v = mfind(id)
+
+        if (v.isDefined) id + "(@" + v.get._1 + ")" else id
+    }
+}
+
+/**
+ * Statistics holder for visor alert.
+ */
+sealed private case class VisorStats(
+    var cnt: Int = 0,
+    var firstSnd: Long = 0,
+    var lastSnd: Long = 0
+)
+
+/**
+ * Companion object that does initialization of the command.
+ */
+object VisorAlertCommand {
+    addHelp(
+        name = "alert",
+        shortInfo = "Email alerts for user-defined events.",
+        longInfo = Seq(
+            "Generates email alerts for user-defined events.",
+            "Node events and grid-wide events are defined via mnemonics."
+        ),
+        spec = Seq(
+            "alert",
+            "alert -u {-id=<alert-id>|-a}",
+            "alert -r {-t=<sec>} {-<metric>=<condition><value>} ... 
{-<metric>=<condition><value>}"
+        ),
+        args = Seq(
+            "-u" -> Seq(
+                "Unregisters alert(s). Either '-a' flag or '-id' parameter is 
required.",
+                "Note that only one of the '-u' or '-r' is allowed.",
+                "If neither '-u' or '-r' provided - all alerts will be 
printed."
+            ),
+            "-a" ->
+                "When provided with '-u' - all alerts will be unregistered.",
+            ("-id=<alert-id>",
+                "When provided with '-u' - alert with matching ID will be 
unregistered" +
+                "Note you can also use '@a0' ... '@an' variables as shortcut 
to <alert-id>."),
+            "-r" -> Seq(
+                "Register new alert with mnemonic predicate(s).",
+                "Note that only one of the '-u' or '-r' is allowed.",
+                "If neither '-u' or '-r' provided - all alerts will be 
printed.",
+                "",
+                "NOTE: Email settings can be specified in GridGain 
configuration file.",
+                "      Email notification will be sent for the alert only when 
all",
+                "      provided mnemonic predicates evaluate to 'true'."
+            ),
+            "-t" -> Seq(
+                "Defines notification frequency in seconds. Default is 15 
minutes.",
+                "This parameter can only appear with '-r'."
+             ),
+            "-<metric>" -> Seq(
+                "This defines a mnemonic for the metric that will be 
measured:",
+                "",
+                "Grid-wide metrics (not node specific):",
+                "   cc - Total number of available CPUs in the grid.",
+                "   nc - Total number of nodes in the grid.",
+                "   hc - Total number of physical hosts in the grid.",
+                "   cl - Current average CPU load (in %) in the grid.",
+                "",
+                "Per-node current metrics:",
+                "   aj - Active jobs on the node.",
+                "   cj - Cancelled jobs on the node.",
+                "   tc - Thread count on the node.",
+                "   ut - Up time on the node.",
+                "       Note: <num> can have 's', 'm', or 'h' suffix 
indicating",
+                "       seconds, minutes, and hours. By default (no suffix 
provided)",
+                "       value is assumed to be in milliseconds.",
+                "   je - Job execute time on the node.",
+                "   jw - Job wait time on the node.",
+                "   wj - Waiting jobs count on the node.",
+                "   rj - Rejected jobs count on the node.",
+                "   hu - Heap memory used (in MB) on the node.",
+                "   cd - Current CPU load on the node.",
+                "   hm - Heap memory maximum (in MB) on the node."
+            ),
+            "<condition>" -> Seq(
+                "Comparison part of the mnemonic predicate:",
+                "   eq - Equal '=' to '<value>' number.",
+                "   neq - Not equal '!=' to '<value>' number.",
+                "   gt - Greater than '>' to '<value>' number.",
+                "   gte - Greater than or equal '>=' to '<value>' number.",
+                "   lt - Less than '<' to 'NN' number.",
+                "   lte - Less than or equal '<=' to '<value>' number."
+            )
+        ),
+        examples = Seq(
+            "alert" ->
+                "Prints all currently registered alerts.",
+            "alert -u -a" ->
+                "Unregisters all currently registered alerts.",
+            "alert -u -id=12345678" ->
+                "Unregisters alert with provided ID.",
+            "alert -u -id=@a0" ->
+                "Unregisters alert with provided ID taken from '@a0' memory 
variable.",
+            "alert -r -t=900 -cc=gte4 -cl=gt50" ->
+                "Notify every 15 min if grid has >= 4 CPUs and > 50% CPU load."
+        ),
+        ref = VisorConsoleCommand(cmd.alert, cmd.alert)
+    )
+
+    /** Singleton command. */
+    private val cmd = new VisorAlertCommand
+
+    addCloseCallback(() => {
+        cmd.reset()
+    })
+
+    /**
+     * Singleton.
+     */
+    def apply() = cmd
+
+    /**
+     * Implicit converter from visor to commands "pimp".
+     *
+     * @param vs Visor tagging trait.
+     */
+    implicit def fromAlert2Visor(vs: VisorTag): VisorAlertCommand = cmd
+}

Reply via email to