ignite-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From vkuliche...@apache.org
Subject [14/50] [abbrv] ignite git commit: IGNITE-2919 Implemented script execution when alert triggered.
Date Fri, 13 May 2016 19:20:46 GMT
 IGNITE-2919 Implemented script execution when alert triggered.


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

Branch: refs/heads/ignite-2949
Commit: 9a96d1117dded23811bb38305dbab1ad5bd3f3f9
Parents: 7c3ef23
Author: vsisko <vsisko@gridgain.com>
Authored: Thu May 12 16:03:38 2016 +0700
Committer: Alexey Kuznetsov <akuznetsov@apache.org>
Committed: Thu May 12 16:08:22 2016 +0700

----------------------------------------------------------------------
 .../commands/alert/VisorAlertCommand.scala      | 358 ++++++++++++++-----
 .../scala/org/apache/ignite/visor/visor.scala   |  32 +-
 .../commands/alert/VisorAlertCommandSpec.scala  |   2 +-
 3 files changed, 284 insertions(+), 108 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ignite/blob/9a96d111/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
index 85851a8..0ccca7f 100644
--- 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
@@ -17,6 +17,10 @@
 
 package org.apache.ignite.visor.commands.alert
 
+import java.io.{File, FileNotFoundException}
+import java.util.UUID
+import java.util.concurrent.atomic._
+
 import org.apache.ignite._
 import org.apache.ignite.cluster.ClusterNode
 import org.apache.ignite.events.EventType._
@@ -25,14 +29,14 @@ import org.apache.ignite.internal.util.scala.impl
 import org.apache.ignite.internal.util.{IgniteUtils => U}
 import org.apache.ignite.lang.IgnitePredicate
 import org.apache.ignite.visor.VisorTag
+import org.apache.ignite.visor.commands.alert.VisorAlertCommand._
 import org.apache.ignite.visor.commands.common.{VisorConsoleCommand, VisorTextTable}
 import org.apache.ignite.visor.visor._
 
-import java.util.UUID
-import java.util.concurrent.atomic._
-
-import scala.collection.immutable._
+import scala.collection.immutable.HashMap
+import scala.collection.mutable
 import scala.language.implicitConversions
+import scala.sys.process.{Process, ProcessLogger}
 import scala.util.control.Breaks._
 
 /**
@@ -56,6 +60,8 @@ import scala.util.control.Breaks._
  *
  * ====Arguments====
  * {{{
+ *     -n
+ *         Alert name
  *     -u
  *         Unregisters alert(s). Either '-a' flag or '-id' parameter is required.
  *         Note that only one of the '-u' or '-r' is allowed.
@@ -68,10 +74,19 @@ import scala.util.control.Breaks._
  *         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.
+ *         Defines notification frequency in seconds. Default is 1 minute.
  *         This parameter can only appear with '-r'.
+ *     -s
+ *         Define script for execution when alert triggered.
+ *         For configuration of throttle period see -i argument.
+ *         Script will receive following arguments:
+ *             1) Alert name or alert ID when name is not defined.
+ *             2) Alert condition as string.
+ *             3, ...) Values of alert conditions ordered as in alert command.
+ *     -i
+ *         Configure alert notification minimal throttling interval in seconds. Default is
1 minute.
+ *
  *     -<metric>
  *         This defines a mnemonic for the metric that will be measured:
  *
@@ -117,19 +132,19 @@ import scala.util.control.Breaks._
  *         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.
+ *     alert "-r -n=Nodes -t=15 -nc=gte3 -s=/home/user/scripts/alert.sh -i=300"
+ *         Notify every 15 second if grid has >= 3 nodes and execute script "/home/user/scripts/alert.sh"
with
+ *         repeat interval not less than 5 min.
  * }}}
  */
 class VisorAlertCommand extends VisorConsoleCommand {
     @impl protected val name = "alert"
 
-    /** 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]
+    private var sent = new HashMap[String, Long]
 
     /** Map of alert statistics. */
     private var stats = new HashMap[String, VisorStats]
@@ -190,42 +205,52 @@ class VisorAlertCommand extends VisorConsoleCommand {
     }
 
     /**
+     * Create function to check specific node condition.
+     *
      * @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 = {
+    private def makeNodeFilter(exprStr: String, value: (ClusterNode) => Long): Option[(ClusterNode)
=> (Long, 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 IgniteException("Invalid expression: " + exprStr)
+        expr match {
+            case Some(f) => Some((n: ClusterNode) => {
+                val v = value(n)
+
+                (v, f.apply(v))
+            })
+
+            case _ => throw new IgniteException("Invalid expression: " + exprStr)
+        }
     }
 
     /**
+     * Create function to check specific grid condition.
+     *
      * @param exprStr Expression string.
-     * @param f Grid filter
      * @param value Value generator.
      */
-    private def makeGridFilter(exprStr: String, f: () => Boolean, value: () => Long):
() => Boolean = {
+    private def makeGridFilter(exprStr: String, value: () => Long): Option[() => (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 IgniteException("Invalid expression: " + exprStr)
+        expr match {
+            case Some(f) =>
+                Some(() => {
+                    val v = value()
+
+                    (v, f.apply(v))
+                })
+            case _ =>
+                throw new IgniteException("Invalid expression: " + exprStr)
+        }
     }
 
     /**
@@ -238,40 +263,30 @@ class VisorAlertCommand extends VisorConsoleCommand {
             if (!isConnected)
                 adviseToConnect()
             else {
-                val dfltNodeF = (_: ClusterNode) => true
-                val dfltGridF = () => true
-
-                var nf = dfltNodeF
-                var gf = dfltGridF
-
+                var name: Option[String] = None
+                var script: Option[String] = None
+                val conditions = mutable.ArrayBuffer.empty[VisorAlertCondition]
                 var freq = DFLT_FREQ
+                var interval = DFLT_FREQ
 
                 try {
                     args.foreach(arg => {
-                        val (alertName, v) = arg
-
-                        alertName match {
-                            // Grid-wide metrics (not node specific).
-                            case "cc" if v != null => gf = makeGridFilter(v, gf, () =>
ignite.cluster.metrics().getTotalCpus)
-                            case "nc" if v != null => gf = makeGridFilter(v, gf, () =>
ignite.cluster.nodes().size)
-                            case "hc" if v != null => gf = makeGridFilter(v, gf, () =>
U.neighborhood(ignite.cluster.nodes()).size)
-                            case "cl" if v != null => gf = makeGridFilter(v, gf, () =>
(ignite.cluster.metrics().getAverageCpuLoad * 100).toLong)
-
-                            // Per-node current metrics.
-                            case "aj" if v != null => nf = makeNodeFilter(v, nf, (n: ClusterNode)
=> n.metrics().getCurrentActiveJobs)
-                            case "cj" if v != null => nf = makeNodeFilter(v, nf, (n: ClusterNode)
=> n.metrics().getCurrentCancelledJobs)
-                            case "tc" if v != null => nf = makeNodeFilter(v, nf, (n: ClusterNode)
=> n.metrics().getCurrentThreadCount)
-                            case "ut" if v != null => nf = makeNodeFilter(v, nf, (n: ClusterNode)
=> n.metrics().getUpTime)
-                            case "je" if v != null => nf = makeNodeFilter(v, nf, (n: ClusterNode)
=> n.metrics().getCurrentJobExecuteTime)
-                            case "jw" if v != null => nf = makeNodeFilter(v, nf, (n: ClusterNode)
=> n.metrics().getCurrentJobWaitTime)
-                            case "wj" if v != null => nf = makeNodeFilter(v, nf, (n: ClusterNode)
=> n.metrics().getCurrentWaitingJobs)
-                            case "rj" if v != null => nf = makeNodeFilter(v, nf, (n: ClusterNode)
=> n.metrics().getCurrentRejectedJobs)
-                            case "hu" if v != null => nf = makeNodeFilter(v, nf, (n: ClusterNode)
=> n.metrics().getHeapMemoryUsed)
-                            case "hm" if v != null => nf = makeNodeFilter(v, nf, (n: ClusterNode)
=> n.metrics().getHeapMemoryMaximum)
-                            case "cd" if v != null => nf = makeNodeFilter(v, nf, (n: ClusterNode)
=> (n.metrics().getCurrentCpuLoad * 100).toLong)
+                        val (n, v) = arg
+
+                        n match {
+                            case c if alertDescr.contains(c) && v != null =>
+                                val meta = alertDescr(c)
+
+                                if (meta.byGrid)
+                                    conditions += VisorAlertCondition(arg, gridFunc = makeGridFilter(v,
meta.gridFunc))
+                                else
+                                    conditions += VisorAlertCondition(arg, nodeFunc = makeNodeFilter(v,
meta.nodeFunc))
 
                             // Other tags.
+                            case "n" if v != null => name = Some(v)
                             case "t" if v != null => freq = v.toLong
+                            case "s" if v != null => script = Option(v)
+                            case "i" if v != null => interval = v.toLong
                             case "r" => () // Skipping.
                             case _ => throw new IgniteException("Invalid argument: " +
makeArg(arg))
                         }
@@ -289,7 +304,7 @@ class VisorAlertCommand extends VisorConsoleCommand {
                         break()
                 }
 
-                if (nf == null && gf == null) {
+                if (conditions.isEmpty) {
                     scold("No predicates have been provided in args: " + makeArgs(args))
 
                     break()
@@ -297,14 +312,19 @@ class VisorAlertCommand extends VisorConsoleCommand {
 
                 val alert = new VisorAlert(
                     id = id8,
-                    nodeFilter = nf,
-                    gridFilter = gf,
-                    perNode = nf != dfltNodeF,
-                    perGrid = gf != dfltGridF,
+                    name = name,
+                    conditions = conditions.toSeq,
+                    perGrid = conditions.exists(_.gridFunc.isDefined),
+                    perNode = conditions.exists(_.nodeFunc.isDefined),
                     spec = makeArgs(args),
+                    conditionSpec = makeArgs(conditions.map(_.arg)),
                     freq = freq,
                     createdOn = System.currentTimeMillis(),
-                    varName = setVar(id8, "a")
+                    varName = setVar(id8, "a"),
+                    notification = new VisorAlertNotification(
+                        script = script,
+                        throttleInterval = interval * 1000
+                    )
                 )
 
                 // Subscribe for node metric updates - if needed.
@@ -338,39 +358,46 @@ class VisorAlertCommand extends VisorConsoleCommand {
                         alerts foreach (t => {
                             val (id, alert) = t
 
-                            var nb = false
-                            var gb = false
+                            var check = (true, true)
 
-                            try {
-                                nb = alert.nodeFilter(node)
-                                gb = alert.gridFilter()
-                            }
+                            val values = mutable.ArrayBuffer.empty[String]
+
+                            try
+                                check = alert.conditions.foldLeft(check) {
+                                    (res, cond) => {
+                                        val gridRes = cond.gridFunc.map(f => {
+                                            val (value, check) = f()
+
+                                            values += value.toString
+
+                                            check
+                                        }).getOrElse(true)
+
+                                        val nodeRes = cond.nodeFunc.map(f => {
+                                            val (value, check) = f(node)
+
+                                            values += value.toString
+
+                                            check
+                                        }).getOrElse(true)
+
+                                        (res._1 && gridRes) -> (res._2 &&
nodeRes)
+                                    }
+                                }
                             catch {
                                 // In case of exception (like an empty projection) - simply
return.
                                 case _: Throwable => return true
                             }
 
-                            if (nb && gb) {
+                            if (check._1 && check._2) {
                                 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
+                                go = (now - sent.getOrElse(id, 0L)) / 1000 >= alert.freq
 
                                 if (go) {
-                                    // Update throttling.
-                                    if (nb && alert.perNode)
-                                        sent = sent + (nKey -> now)
-
-                                    if (gb && alert.perGrid)
-                                        sent = sent + (gKey -> now)
+                                    sent = sent + (id -> now)
 
                                     val stat: VisorStats = stats(id)
 
@@ -394,8 +421,11 @@ class VisorAlertCommand extends VisorConsoleCommand {
                                         "]"
                                     )
 
+                                    executeAlertScript(alert, node, values.toSeq)
+
                                     last10 = VisorSentAlert(
                                         id = alert.id,
+                                        name = alert.name.getOrElse("(Not set)"),
                                         spec = alert.spec,
                                         createdOn = alert.createdOn,
                                         sentTs = now
@@ -405,6 +435,8 @@ class VisorAlertCommand extends VisorConsoleCommand {
                                         last10 = last10.take(10)
                                 }
                             }
+                            else
+                                alert.notification.notified = false
                         })
 
                     true
@@ -539,10 +571,10 @@ class VisorAlertCommand extends VisorConsoleCommand {
         else {
             val last10T = VisorTextTable()
 
-            last10T #= ("ID(@)", "Spec", "Sent", "Registered", "Count")
+            last10T #= ("ID(@)/Name", "Spec", "Sent", "Registered", "Count")
 
             last10.foreach((a: VisorSentAlert) => last10T += (
-                a.idVar,
+                a.idVar + "/" + a.name,
                 a.spec,
                 formatDateTime(a.sentTs),
                 formatDateTime(a.createdOn),
@@ -557,7 +589,7 @@ class VisorAlertCommand extends VisorConsoleCommand {
         if (alerts.nonEmpty) {
             val tbl = new VisorTextTable()
 
-            tbl #= ("ID(@)", "Spec", "Count", "Registered", "First Send", "Last Send")
+            tbl #= ("ID(@)/Name", "Spec", "Count", "Registered", "First Send", "Last Send")
 
             val sorted = alerts.values.toSeq.sortWith(_.varName < _.varName)
 
@@ -565,7 +597,7 @@ class VisorAlertCommand extends VisorConsoleCommand {
                 val stat = stats(a.id)
 
                 tbl += (
-                    a.id + "(@" + a.varName + ')',
+                    a.id + "(@" + a.varName + ')' + "/" + a.name.getOrElse("Not set"),
                     a.spec,
                     stat.cnt,
                     formatDateTime(a.createdOn),
@@ -601,21 +633,74 @@ class VisorAlertCommand extends VisorConsoleCommand {
 
         ""
     }
+
+    /**
+     * Try to execute specified script on alert.
+     *
+     * @param alert Alarmed alert.
+     * @param node Node where alert is alarmed.
+     * @param values Values ith that alert is generated.
+     */
+    private def executeAlertScript(alert: VisorAlert, node: ClusterNode, values: Seq[String])
{
+        val n = alert.notification
+
+        try {
+            n.script.foreach(script => {
+                if (!n.notified && (n.notifiedTime < 0 || (System.currentTimeMillis()
- n.notifiedTime) > n.throttleInterval)) {
+                    val scriptFile = new File(script)
+
+                    if (!scriptFile.exists())
+                        throw new FileNotFoundException("Script/executable not found: " +
script)
+
+                    val scriptFolder = scriptFile.getParentFile
+
+                    val p = Process(Seq(script, alert.name.getOrElse(alert.id), alert.conditionSpec)
++ values,
+                        Some(scriptFolder))
+
+                    p.run(ProcessLogger((fn: String) => {}))
+
+                    n.notifiedTime = System.currentTimeMillis()
+
+                    n.notified = true
+                }
+            })
+        }
+        catch {
+            case e: Throwable => logText("Script execution failed [" +
+                    "id=" + alert.id + "(@" + alert.varName + "), " +
+                    "error=" + e.getMessage + ", " +
+                "]")
+        }
+    }
 }
 
 /**
  * Visor alert.
+ *
+ * @param id Alert id.
+ * @param name Alert name.
+ * @param conditions List of alert condition metadata.
+ * @param perGrid Condition per grid exists.
+ * @param perNode Condition per node exists.
+ * @param freq Freq of alert check.
+ * @param spec Alert command specification.
+ * @param conditionSpec Alert condition command specification.
+ * @param varName Alert id short name.
+ * @param createdOn Timestamp of alert creation.
+ * @param notification Alert notification information.
  */
-sealed private case class VisorAlert(
+private case class VisorAlert(
     id: String,
-    nodeFilter: ClusterNode => Boolean,
-    gridFilter: () => Boolean,
+    name: Option[String],
+    conditions: Seq[VisorAlertCondition],
+    perGrid: Boolean,
+    perNode: Boolean,
     freq: Long,
     spec: String,
+    conditionSpec: String,
     varName: String,
-    perNode: Boolean,
-    perGrid: Boolean,
-    createdOn: Long
+    createdOn: Long,
+    notification: VisorAlertNotification
 ) {
     assert(id != null)
     assert(spec != null)
@@ -623,10 +708,41 @@ sealed private case class VisorAlert(
 }
 
 /**
+  * Visor alert notification information.
+  *
+  * @param script Script to execute on alert firing.
+  * @param throttleInterval Minimal interval between script execution.
+  * @param notified `True` when on previous check of condition alert was fired or `false`
otherwise.
+  * @param notifiedTime Time of first alert notification in series of firings.
+  */
+private case class VisorAlertNotification(
+    script: Option[String],
+    throttleInterval: Long,
+    var notified: Boolean = false,
+    var notifiedTime: Long = -1L
+) {
+    assert(script != null)
+}
+
+/**
+ * Visor alert condition information.
+ *
+ * @param arg Configuration argument.
+ * @param gridFunc Function to check grid condition.
+ * @param nodeFunc Function to check node condition.
+ */
+private case class VisorAlertCondition(
+    arg: (String, String),
+    gridFunc: Option[() => (Long, Boolean)] = None,
+    nodeFunc: Option[(ClusterNode) => (Long, Boolean)] = None
+)
+
+/**
  * Snapshot of the sent alert.
  */
 private case class VisorSentAlert(
     id: String,
+    name: String,
     sentTs: Long,
     createdOn: Long,
     spec: String
@@ -644,16 +760,57 @@ private case class VisorSentAlert(
 /**
  * Statistics holder for visor alert.
  */
-sealed private case class VisorStats(
+private case class VisorStats(
     var cnt: Int = 0,
     var firstSnd: Long = 0,
     var lastSnd: Long = 0
 )
 
 /**
+  * Metadata object for visor alert.
+  *
+  * @param byGrid If `true` then `gridFunc` should be used.
+  * @param gridFunc Function to extract value to check in case of alert for grid metrics.
+  * @param nodeFunc Function to extract value to check in case of alert for node metrics
+  */
+private case class VisorAlertMeta(
+    byGrid: Boolean,
+    gridFunc: () => Long,
+    nodeFunc: (ClusterNode) => Long
+)
+
+/**
  * Companion object that does initialization of the command.
  */
 object VisorAlertCommand {
+    /** Default alert frequency. */
+    val DFLT_FREQ = 60L
+
+    private val dfltNodeValF = (_: ClusterNode) => 0L
+    private val dfltGridValF = () => 0L
+
+    private def cl(): IgniteCluster = ignite.cluster()
+
+    private[this] val BY_GRID = true
+    private[this] val BY_NODE = false
+
+    private val alertDescr = Map(
+        "cc" -> VisorAlertMeta(BY_GRID, () => cl().metrics().getTotalCpus, dfltNodeValF),
+        "nc" -> VisorAlertMeta(BY_GRID, () => cl().nodes().size, dfltNodeValF),
+        "hc" -> VisorAlertMeta(BY_GRID, () => U.neighborhood(cl().nodes()).size, dfltNodeValF),
+        "cl" -> VisorAlertMeta(BY_GRID, () => (cl().metrics().getAverageCpuLoad * 100).toLong,
dfltNodeValF),
+        "aj" -> VisorAlertMeta(BY_NODE, dfltGridValF, (node) => node.metrics().getCurrentActiveJobs),
+        "cj" -> VisorAlertMeta(BY_NODE, dfltGridValF, (node) => node.metrics().getCurrentCancelledJobs),
+        "tc" -> VisorAlertMeta(BY_NODE, dfltGridValF, (node) => node.metrics().getCurrentThreadCount),
+        "ut" -> VisorAlertMeta(BY_NODE, dfltGridValF, (node) => node.metrics().getUpTime),
+        "je" -> VisorAlertMeta(BY_NODE, dfltGridValF, (node) => node.metrics().getCurrentJobExecuteTime),
+        "jw" -> VisorAlertMeta(BY_NODE, dfltGridValF, (node) => node.metrics().getCurrentJobWaitTime),
+        "wj" -> VisorAlertMeta(BY_NODE, dfltGridValF, (node) => node.metrics().getCurrentWaitingJobs),
+        "rj" -> VisorAlertMeta(BY_NODE, dfltGridValF, (node) => node.metrics().getCurrentRejectedJobs),
+        "hu" -> VisorAlertMeta(BY_NODE, dfltGridValF, (node) => node.metrics().getHeapMemoryUsed),
+        "cd" -> VisorAlertMeta(BY_NODE, dfltGridValF, (node) => node.metrics().getHeapMemoryMaximum),
+        "hm" -> VisorAlertMeta(BY_NODE, dfltGridValF, (node) => (node.metrics().getCurrentCpuLoad
* 100).toLong))
+
     /** Singleton command. */
     private val cmd = new VisorAlertCommand
 
@@ -670,6 +827,8 @@ object VisorAlertCommand {
             "alert -r {-t=<sec>} {-<metric>=<condition><value>} ...
{-<metric>=<condition><value>}"
         ),
         args = Seq(
+            "-n" ->
+                "Alert name",
             "-u" -> Seq(
                 "Unregisters alert(s). Either '-a' flag or '-id' parameter is required.",
                 "Note that only one of the '-u' or '-r' is allowed.",
@@ -686,9 +845,18 @@ object VisorAlertCommand {
                 "If neither '-u' or '-r' provided - all alerts will be printed."
             ),
             "-t" -> Seq(
-                "Defines notification frequency in seconds. Default is 15 minutes.",
+                "Defines notification frequency in seconds. Default is 1 minute.",
                 "This parameter can only appear with '-r'."
-             ),
+            ),
+            "-s" -> Seq(
+                "Define script for execution when alert triggered.",
+                "For configuration of throttle period see -i argument.",
+                "Script will receive following arguments:",
+                "    1) Alert name or alert ID when name is not defined.",
+                "    2) Alert condition as string.",
+                "    3, ...) Values of alert conditions ordered as in alert command."
+            ),
+            "-i" -> "Configure alert notification minimal throttling interval. Default
is 1 minute.",
             "-<metric>" -> Seq(
                 "This defines a mnemonic for the metric that will be measured:",
                 "",
@@ -734,8 +902,12 @@ object VisorAlertCommand {
             "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."
+                "Notify every 15 min if grid has >= 4 CPUs and > 50% CPU load.",
+            "alert \"-r -n=Nodes -t=15 -nc=gte3 -s=/home/user/scripts/alert.sh -i=300" ->
+                ("Notify every 15 second if grid has >= 3 nodes and execute script \"/home/user/scripts/alert.sh\"
with " +
+                "repeat interval not less than 5 min.")
         ),
+
         emptyArgs = cmd.alert,
         withArgs = cmd.alert
     )

http://git-wip-us.apache.org/repos/asf/ignite/blob/9a96d111/modules/visor-console/src/main/scala/org/apache/ignite/visor/visor.scala
----------------------------------------------------------------------
diff --git a/modules/visor-console/src/main/scala/org/apache/ignite/visor/visor.scala b/modules/visor-console/src/main/scala/org/apache/ignite/visor/visor.scala
index 3b4bf82..7f7d1f9 100644
--- a/modules/visor-console/src/main/scala/org/apache/ignite/visor/visor.scala
+++ b/modules/visor-console/src/main/scala/org/apache/ignite/visor/visor.scala
@@ -1101,20 +1101,24 @@ object visor extends VisorTag {
             }
 
         try
-            if (s.startsWith("lte")) // <=
-                Some(_ <= value(s.substring(3)))
-            else if (s.startsWith("lt")) // <
-                Some(_ < value(s.substring(2)))
-            else if (s.startsWith("gte")) // >=
-                Some(_ >= value(s.substring(3)))
-            else if (s.startsWith("gt")) // >
-                Some(_ > value(s.substring(2)))
-            else if (s.startsWith("eq")) // ==
-                Some(_ == value(s.substring(2)))
-            else if (s.startsWith("neq")) // !=
-                Some(_ != value(s.substring(3)))
-            else
-                None
+            Option(
+                if (s == null)
+                    null
+                else if (s.startsWith("lte")) // <=
+                    _ <= value(s.substring(3))
+                else if (s.startsWith("lt"))  // <
+                    _ < value(s.substring(2))
+                else if (s.startsWith("gte")) // >=
+                    _ >= value(s.substring(3))
+                else if (s.startsWith("gt"))  // >
+                    _ > value(s.substring(2))
+                else if (s.startsWith("eq"))  // ==
+                    _ == value(s.substring(2))
+                else if (s.startsWith("neq")) // !=
+                    _ != value(s.substring(3))
+                else
+                    null
+            )
         catch {
             case e: Throwable => None
         }

http://git-wip-us.apache.org/repos/asf/ignite/blob/9a96d111/modules/visor-console/src/test/scala/org/apache/ignite/visor/commands/alert/VisorAlertCommandSpec.scala
----------------------------------------------------------------------
diff --git a/modules/visor-console/src/test/scala/org/apache/ignite/visor/commands/alert/VisorAlertCommandSpec.scala
b/modules/visor-console/src/test/scala/org/apache/ignite/visor/commands/alert/VisorAlertCommandSpec.scala
index ec7d1b5..998070c 100644
--- a/modules/visor-console/src/test/scala/org/apache/ignite/visor/commands/alert/VisorAlertCommandSpec.scala
+++ b/modules/visor-console/src/test/scala/org/apache/ignite/visor/commands/alert/VisorAlertCommandSpec.scala
@@ -124,7 +124,7 @@ class VisorAlertCommandSpec extends VisorRuntimeBaseSpec(1) {
 
         it("should print error messages on incorrect alerts") {
             try {
-                matchOut(visor.alert("-r -t=5"), "Alert.+registered.")
+                checkOut(visor.alert("-r -t=5"), "No predicates have been provided in args")
 
                 checkOut(visor.alert("-r -UNKNOWN_KEY=lt20"), "Invalid argument")
 


Mime
View raw message