# 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 +}