You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by jo...@apache.org on 2016/04/07 20:03:24 UTC

[1/2] ambari git commit: AMBARI-15745 - Create Alert For Reporting Potential Issues With Slow REST Responses (jonathanhurley)

Repository: ambari
Updated Branches:
  refs/heads/trunk 5f0a09690 -> 28f1a958d


http://git-wip-us.apache.org/repos/asf/ambari/blob/28f1a958/ambari-server/src/test/java/org/apache/ambari/server/api/services/AmbariMetaInfoTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/api/services/AmbariMetaInfoTest.java b/ambari-server/src/test/java/org/apache/ambari/server/api/services/AmbariMetaInfoTest.java
index 10b92d4..5dc7f3b 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/api/services/AmbariMetaInfoTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/api/services/AmbariMetaInfoTest.java
@@ -18,12 +18,33 @@
 
 package org.apache.ambari.server.api.services;
 
-import com.google.gson.Gson;
-import com.google.inject.AbstractModule;
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-import com.google.inject.util.Modules;
-import junit.framework.Assert;
+import static org.easymock.EasyMock.createNiceMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.reset;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import java.io.File;
+import java.io.FileReader;
+import java.lang.reflect.Field;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Properties;
+import java.util.Set;
+import java.util.UUID;
+
+import javax.persistence.EntityManager;
+
 import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.StackAccessException;
 import org.apache.ambari.server.configuration.Configuration;
@@ -77,31 +98,13 @@ import org.junit.rules.TemporaryFolder;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import javax.persistence.EntityManager;
-import java.io.File;
-import java.io.FileReader;
-import java.lang.reflect.Field;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
-import java.util.Properties;
-import java.util.Set;
-import java.util.UUID;
+import com.google.gson.Gson;
+import com.google.inject.AbstractModule;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.util.Modules;
 
-import static org.easymock.EasyMock.createNiceMock;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.replay;
-import static org.easymock.EasyMock.reset;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
+import junit.framework.Assert;
 
 public class AmbariMetaInfoTest {
 
@@ -1858,7 +1861,7 @@ public class AmbariMetaInfoTest {
 
     AlertDefinitionDAO dao = injector.getInstance(AlertDefinitionDAO.class);
     List<AlertDefinitionEntity> definitions = dao.findAll(clusterId);
-    assertEquals(9, definitions.size());
+    assertEquals(10, definitions.size());
 
     // figure out how many of these alerts were merged into from the
     // non-stack alerts.json
@@ -1871,7 +1874,7 @@ public class AmbariMetaInfoTest {
     }
 
     assertEquals(1, hostAlertCount);
-    assertEquals(8, definitions.size() - hostAlertCount);
+    assertEquals(9, definitions.size() - hostAlertCount);
 
     for (AlertDefinitionEntity definition : definitions) {
       definition.setScheduleInterval(28);
@@ -1881,7 +1884,7 @@ public class AmbariMetaInfoTest {
     metaInfo.reconcileAlertDefinitions(clusters);
 
     definitions = dao.findAll();
-    assertEquals(9, definitions.size());
+    assertEquals(10, definitions.size());
 
     for (AlertDefinitionEntity definition : definitions) {
       assertEquals(28, definition.getScheduleInterval().intValue());
@@ -1890,7 +1893,7 @@ public class AmbariMetaInfoTest {
     // find all enabled for the cluster should find 6 (the ones from HDFS;
     // it will not find the agent alert since it's not bound to the cluster)
     definitions = dao.findAllEnabled(cluster.getClusterId());
-    assertEquals(8, definitions.size());
+    assertEquals(9, definitions.size());
 
     // create new definition
     AlertDefinitionEntity entity = new AlertDefinitionEntity();
@@ -1909,19 +1912,19 @@ public class AmbariMetaInfoTest {
 
     // verify the new definition is found (6 HDFS + 1 new one)
     definitions = dao.findAllEnabled(cluster.getClusterId());
-    assertEquals(9, definitions.size());
+    assertEquals(10, definitions.size());
 
     // reconcile, which should disable our bad definition
     metaInfo.reconcileAlertDefinitions(clusters);
 
     // find all enabled for the cluster should find 6
     definitions = dao.findAllEnabled(cluster.getClusterId());
-    assertEquals(8, definitions.size());
+    assertEquals(9, definitions.size());
 
     // find all should find 6 HDFS + 1 disabled + 1 agent alert + 2 server
     // alerts
     definitions = dao.findAll();
-    assertEquals(10, definitions.size());
+    assertEquals(11, definitions.size());
 
     entity = dao.findById(entity.getDefinitionId());
     assertFalse(entity.getEnabled());

http://git-wip-us.apache.org/repos/asf/ambari/blob/28f1a958/ambari-server/src/test/java/org/apache/ambari/server/metadata/AgentAlertDefinitionsTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/metadata/AgentAlertDefinitionsTest.java b/ambari-server/src/test/java/org/apache/ambari/server/metadata/AgentAlertDefinitionsTest.java
index 9d58b94..f5ce11e 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/metadata/AgentAlertDefinitionsTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/metadata/AgentAlertDefinitionsTest.java
@@ -19,8 +19,6 @@ package org.apache.ambari.server.metadata;
 
 import java.util.List;
 
-import junit.framework.Assert;
-
 import org.apache.ambari.server.controller.RootServiceResponseFactory.Components;
 import org.apache.ambari.server.orm.InMemoryDefaultTestModule;
 import org.apache.ambari.server.state.alert.AlertDefinition;
@@ -30,6 +28,8 @@ import org.junit.Test;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 
+import junit.framework.Assert;
+
 /**
  * Tets {@link AmbariServiceAlertDefinitions}.
  */
@@ -66,7 +66,7 @@ public class AgentAlertDefinitionsTest {
   public void testLoadingServertAlerts() {
     AmbariServiceAlertDefinitions ambariServiceAlertDefinitions = m_injector.getInstance(AmbariServiceAlertDefinitions.class);
     List<AlertDefinition> definitions = ambariServiceAlertDefinitions.getServerDefinitions();
-    Assert.assertEquals(2, definitions.size());
+    Assert.assertEquals(3, definitions.size());
 
     for (AlertDefinition definition : definitions) {
       Assert.assertEquals(Components.AMBARI_SERVER.name(),


[2/2] ambari git commit: AMBARI-15745 - Create Alert For Reporting Potential Issues With Slow REST Responses (jonathanhurley)

Posted by jo...@apache.org.
AMBARI-15745 - Create Alert For Reporting Potential Issues With Slow REST Responses (jonathanhurley)


Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/28f1a958
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/28f1a958
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/28f1a958

Branch: refs/heads/trunk
Commit: 28f1a958d09216e7a69d42ef3b0796c90cdcc0c4
Parents: 5f0a096
Author: Jonathan Hurley <jh...@hortonworks.com>
Authored: Wed Apr 6 15:59:54 2016 -0400
Committer: Jonathan Hurley <jh...@hortonworks.com>
Committed: Thu Apr 7 14:02:11 2016 -0400

----------------------------------------------------------------------
 .../server/actionmanager/ActionManager.java     |   4 +-
 .../alerts/AgentHeartbeatAlertRunnable.java     | 171 +++-----
 .../ambari/server/alerts/AlertRunnable.java     | 136 ++++++
 .../alerts/AmbariPerformanceRunnable.java       | 435 +++++++++++++++++++
 .../server/alerts/StaleAlertRunnable.java       | 223 ++++------
 .../server/state/alert/ParameterizedSource.java | 264 +++++++++++
 .../ambari/server/state/alert/ScriptSource.java | 224 +---------
 .../ambari/server/state/alert/ServerSource.java |   2 +-
 .../services/AmbariServerAlertService.java      |  33 +-
 ambari-server/src/main/resources/alerts.json    |  50 +++
 .../alerts/AgentHeartbeatAlertRunnableTest.java |  11 +-
 .../alerts/AmbariPerformanceRunnableTest.java   | 232 ++++++++++
 .../server/alerts/StaleAlertRunnableTest.java   |  56 +--
 .../server/api/services/AmbariMetaInfoTest.java |  77 ++--
 .../metadata/AgentAlertDefinitionsTest.java     |   6 +-
 15 files changed, 1369 insertions(+), 555 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/28f1a958/ambari-server/src/main/java/org/apache/ambari/server/actionmanager/ActionManager.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/actionmanager/ActionManager.java b/ambari-server/src/main/java/org/apache/ambari/server/actionmanager/ActionManager.java
index c12cf6f..71364c2 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/actionmanager/ActionManager.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/actionmanager/ActionManager.java
@@ -223,7 +223,9 @@ public class ActionManager {
     for (Request logicalRequest : topologyManager.getRequests(Collections.<Long>emptySet())) {
       //todo: Request.getStatus() returns HostRoleStatus and we are comparing to RequestStatus
       //todo: for now just compare the names as RequestStatus names are a subset of HostRoleStatus names
-      if (status == null || logicalRequest.getStatus().name().equals(status.name())) {
+      HostRoleStatus logicalRequestStatus = logicalRequest.getStatus();
+      if (status == null || (logicalRequestStatus != null
+          && logicalRequest.getStatus().name().equals(status.name()))) {
         requests.add(logicalRequest.getRequestId());
       }
     }

http://git-wip-us.apache.org/repos/asf/ambari/blob/28f1a958/ambari-server/src/main/java/org/apache/ambari/server/alerts/AgentHeartbeatAlertRunnable.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/alerts/AgentHeartbeatAlertRunnable.java b/ambari-server/src/main/java/org/apache/ambari/server/alerts/AgentHeartbeatAlertRunnable.java
index 0611e23..e520826 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/alerts/AgentHeartbeatAlertRunnable.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/alerts/AgentHeartbeatAlertRunnable.java
@@ -18,45 +18,25 @@
 package org.apache.ambari.server.alerts;
 
 import java.text.MessageFormat;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-
-import org.apache.ambari.server.events.AlertEvent;
-import org.apache.ambari.server.events.AlertReceivedEvent;
-import org.apache.ambari.server.events.publishers.AlertEventPublisher;
-import org.apache.ambari.server.orm.dao.AlertDefinitionDAO;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.orm.entities.AlertDefinitionEntity;
 import org.apache.ambari.server.state.Alert;
 import org.apache.ambari.server.state.AlertState;
 import org.apache.ambari.server.state.Cluster;
-import org.apache.ambari.server.state.Clusters;
 import org.apache.ambari.server.state.Host;
 import org.apache.ambari.server.state.HostState;
 import org.apache.ambari.server.state.services.AmbariServerAlertService;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.inject.Inject;
-import com.google.inject.Provider;
 
 /**
  * The {@link AgentHeartbeatAlertRunnable} is used by the
  * {@link AmbariServerAlertService} to check agent heartbeats and fire alert
  * events when agents are not reachable.
  */
-public class AgentHeartbeatAlertRunnable implements Runnable {
-
-  /**
-   * Logger.
-   */
-  private final static Logger LOG = LoggerFactory.getLogger(AgentHeartbeatAlertRunnable.class);
-
-  /**
-   * The unique name for the alert definition that governs this service.
-   */
-  private static final String HEARTBEAT_DEFINITION_NAME = "ambari_server_agent_heartbeat";
-
+public class AgentHeartbeatAlertRunnable extends AlertRunnable {
   /**
    * Agent initializing message.
    */
@@ -88,99 +68,66 @@ public class AgentHeartbeatAlertRunnable implements Runnable {
   private static final String UNKNOWN_MSG = "{0} has an unknown state of {1}";
 
   /**
-   * Used for looking up alert definitions.
+   * Constructor.
+   *
+   * @param definitionName
    */
-  @Inject
-  private AlertDefinitionDAO m_dao;
+  public AgentHeartbeatAlertRunnable(String definitionName) {
+    super(definitionName);
+  }
 
   /**
-   * Used to get alert definitions to use when generating alert instances.
+   * {@inheritDoc}
    */
-  @Inject
-  private Provider<Clusters> m_clustersProvider;
+  @Override
+  List<Alert> execute(Cluster cluster, AlertDefinitionEntity definition)
+      throws AmbariException {
+    long alertTimestamp = System.currentTimeMillis();
+
+    Collection<Host> hosts = cluster.getHosts();
+
+    List<Alert> alerts = new ArrayList<>(hosts.size());
+    for (Host host : hosts) {
+      String hostName = host.getHostName();
+
+      String alertText;
+      AlertState alertState = AlertState.OK;
+      HostState hostState = host.getState();
+
+      switch (hostState) {
+        case INIT:
+          alertText = MessageFormat.format(INIT_MSG, hostName);
+          break;
+        case HEALTHY:
+          alertText = MessageFormat.format(HEALTHY_MSG, hostName);
+          break;
+        case WAITING_FOR_HOST_STATUS_UPDATES:
+          alertText = MessageFormat.format(STATUS_UPDATE_MSG, hostName);
+          break;
+        case HEARTBEAT_LOST:
+          alertState = AlertState.CRITICAL;
+          alertText = MessageFormat.format(HEARTBEAT_LOST_MSG, hostName);
+          break;
+        case UNHEALTHY:
+          alertState = AlertState.CRITICAL;
+          alertText = MessageFormat.format(UNHEALTHY_MSG, hostName);
+        default:
+          alertState = AlertState.UNKNOWN;
+          alertText = MessageFormat.format(UNKNOWN_MSG, hostName, hostState);
+          break;
+      }
 
-  /**
-   * Publishes {@link AlertEvent} instances.
-   */
-  @Inject
-  private AlertEventPublisher m_alertEventPublisher;
+      Alert alert = new Alert(definition.getDefinitionName(), null, definition.getServiceName(),
+          definition.getComponentName(), hostName, alertState);
 
-  /**
-   * Constructor. Required for type introspection by
-   * {@link AmbariServerAlertService}.
-   */
-  public AgentHeartbeatAlertRunnable() {
-  }
+      alert.setLabel(definition.getLabel());
+      alert.setText(alertText);
+      alert.setTimestamp(alertTimestamp);
+      alert.setCluster(cluster.getClusterName());
 
-  @Override
-  public void run() {
-    try {
-      Map<String, Cluster> clusterMap = m_clustersProvider.get().getClusters();
-      for (Cluster cluster : clusterMap.values()) {
-        AlertDefinitionEntity entity = m_dao.findByName(cluster.getClusterId(),
-            HEARTBEAT_DEFINITION_NAME);
-
-        // skip this cluster if the runnable's alert definition is missing or
-        // disabled
-        if (null == entity || !entity.getEnabled()) {
-          continue;
-        }
-
-        long alertTimestamp = System.currentTimeMillis();
-
-        Map<String, Host> hostMap = m_clustersProvider.get().getHostsForCluster(
-            cluster.getClusterName());
-
-        Set<Entry<String, Host>> entries = hostMap.entrySet();
-        for (Entry<String, Host> entry : entries) {
-          String hostName = entry.getKey();
-          Host host = entry.getValue();
-
-          String alertText;
-          AlertState alertState = AlertState.OK;
-          HostState hostState = host.getState();
-
-          switch (hostState) {
-            case INIT:
-              alertText = MessageFormat.format(INIT_MSG, hostName);
-              break;
-            case HEALTHY:
-              alertText = MessageFormat.format(HEALTHY_MSG, hostName);
-              break;
-            case WAITING_FOR_HOST_STATUS_UPDATES:
-              alertText = MessageFormat.format(STATUS_UPDATE_MSG, hostName);
-              break;
-            case HEARTBEAT_LOST:
-              alertState = AlertState.CRITICAL;
-              alertText = MessageFormat.format(HEARTBEAT_LOST_MSG, hostName);
-              break;
-            case UNHEALTHY:
-              alertState = AlertState.CRITICAL;
-              alertText = MessageFormat.format(UNHEALTHY_MSG, hostName);
-            default:
-              alertState = AlertState.UNKNOWN;
-              alertText = MessageFormat.format(UNKNOWN_MSG, hostName, hostState);
-              break;
-          }
-
-          Alert alert = new Alert(entity.getDefinitionName(), null,
-              entity.getServiceName(), entity.getComponentName(), hostName,
-              alertState);
-
-          alert.setLabel(entity.getLabel());
-          alert.setText(alertText);
-          alert.setTimestamp(alertTimestamp);
-          alert.setCluster(cluster.getClusterName());
-
-          AlertReceivedEvent event = new AlertReceivedEvent(
-              cluster.getClusterId(), alert);
-
-          m_alertEventPublisher.publish(event);
-        }
-      }
-    } catch (Exception exception) {
-      LOG.error("Unable to run the {} alert", HEARTBEAT_DEFINITION_NAME,
-          exception);
+      alerts.add(alert);
     }
+
+    return alerts;
   }
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/28f1a958/ambari-server/src/main/java/org/apache/ambari/server/alerts/AlertRunnable.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/alerts/AlertRunnable.java b/ambari-server/src/main/java/org/apache/ambari/server/alerts/AlertRunnable.java
new file mode 100644
index 0000000..21ae9b0
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/alerts/AlertRunnable.java
@@ -0,0 +1,136 @@
+/**
+ * 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.ambari.server.alerts;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.events.AlertEvent;
+import org.apache.ambari.server.events.AlertReceivedEvent;
+import org.apache.ambari.server.events.publishers.AlertEventPublisher;
+import org.apache.ambari.server.orm.dao.AlertDefinitionDAO;
+import org.apache.ambari.server.orm.entities.AlertDefinitionEntity;
+import org.apache.ambari.server.state.Alert;
+import org.apache.ambari.server.state.Cluster;
+import org.apache.ambari.server.state.Clusters;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+/**
+ * The {@link AlertRunnable} class is used to boilerplate the expected
+ * functionality of {@link Runnable}s which need to eventually create and fire
+ * {@link AlertReceivedEvent}s.
+ * <p/>
+ * Implementations of this class do not need to concern themselves with checking
+ * for whether their alert definition is enabled or with constructing and firing
+ * the alert events.
+ */
+public abstract class AlertRunnable implements Runnable {
+
+  /**
+   * Logger.
+   */
+  private final static Logger LOG = LoggerFactory.getLogger(AlertRunnable.class);
+
+  /**
+   * The alert definition name.
+   */
+  protected final String m_definitionName;
+
+  /**
+   * Used to get alert definitions to use when generating alert instances.
+   */
+  @Inject
+  private Provider<Clusters> m_clustersProvider;
+
+  /**
+   * Used for looking up alert definitions.
+   */
+  @Inject
+  private AlertDefinitionDAO m_dao;
+
+  /**
+   * Publishes {@link AlertEvent} instances.
+   */
+  @Inject
+  private AlertEventPublisher m_alertEventPublisher;
+
+  /**
+   * Constructor.
+   *
+   * @param definitionName
+   *          the definition name (not {@code null}).
+   */
+  public AlertRunnable(String definitionName) {
+    m_definitionName = definitionName;
+  }
+
+  /**
+   * Invoked on subclasses when it is time for them to check their specific
+   * alert criteria. Implementations must return a list of {@link Alert}
+   * instances which will be turned into {@link AlertEvent}s and published.
+   * <p/>
+   * This method will only be invoked if the definition is enabled for the
+   * specified cluster.
+   *
+   * @param cluster
+   *          the cluster for which the alert is executing. If there are
+   *          multiple clusters defined, then this method is called once for
+   *          each.
+   * @param definition
+   *          the information about this concrete alert.
+   * @return a list of {@link Alert} to be used to create {@link AlertEvent}s
+   *         and published with the {@link AlertEventPublisher} (not
+   *         {@code null}).
+   * @throws AmbariException
+   */
+  abstract List<Alert> execute(Cluster cluster, AlertDefinitionEntity definition)
+      throws AmbariException;
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public final void run() {
+    try {
+      Map<String, Cluster> clusterMap = m_clustersProvider.get().getClusters();
+      for (Cluster cluster : clusterMap.values()) {
+        AlertDefinitionEntity definition = m_dao.findByName(cluster.getClusterId(), m_definitionName);
+
+        // skip this cluster if the runnable's alert definition is missing or
+        // disabled
+        if (null == definition || !definition.getEnabled()) {
+          continue;
+        }
+
+        // for every alert generated from the implementation, fire an event
+        List<Alert> alerts = execute(cluster, definition);
+        for (Alert alert : alerts) {
+          AlertReceivedEvent event = new AlertReceivedEvent(cluster.getClusterId(), alert);
+          m_alertEventPublisher.publish(event);
+        }
+      }
+    } catch (Exception exception) {
+      LOG.error("Unable to run the {} alert", m_definitionName, exception);
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/28f1a958/ambari-server/src/main/java/org/apache/ambari/server/alerts/AmbariPerformanceRunnable.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/alerts/AmbariPerformanceRunnable.java b/ambari-server/src/main/java/org/apache/ambari/server/alerts/AmbariPerformanceRunnable.java
new file mode 100644
index 0000000..63fa12d
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/alerts/AmbariPerformanceRunnable.java
@@ -0,0 +1,435 @@
+/**
+ * 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.ambari.server.alerts;
+
+import java.text.MessageFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.actionmanager.ActionManager;
+import org.apache.ambari.server.actionmanager.RequestStatus;
+import org.apache.ambari.server.api.services.BaseRequest;
+import org.apache.ambari.server.controller.AmbariManagementController;
+import org.apache.ambari.server.controller.internal.AbstractControllerResourceProvider;
+import org.apache.ambari.server.controller.internal.ClusterResourceProvider;
+import org.apache.ambari.server.controller.spi.Predicate;
+import org.apache.ambari.server.controller.spi.Request;
+import org.apache.ambari.server.controller.spi.Resource.Type;
+import org.apache.ambari.server.controller.spi.ResourceProvider;
+import org.apache.ambari.server.controller.utilities.PredicateBuilder;
+import org.apache.ambari.server.controller.utilities.PropertyHelper;
+import org.apache.ambari.server.orm.dao.HostRoleCommandDAO;
+import org.apache.ambari.server.orm.entities.AlertDefinitionEntity;
+import org.apache.ambari.server.state.Alert;
+import org.apache.ambari.server.state.AlertState;
+import org.apache.ambari.server.state.Cluster;
+import org.apache.ambari.server.state.alert.AlertDefinition;
+import org.apache.ambari.server.state.alert.AlertDefinitionFactory;
+import org.apache.ambari.server.state.alert.ParameterizedSource.AlertParameter;
+import org.apache.ambari.server.state.alert.ServerSource;
+import org.apache.ambari.server.state.services.AmbariServerAlertService;
+import org.apache.commons.lang.StringUtils;
+import org.apache.commons.lang.math.NumberUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+/**
+ * The {@link AmbariPerformanceRunnable} is used by the
+ * {@link AmbariServerAlertService} to ensure that certain areas of Ambari are
+ * responsive. It performs the following checks:
+ * <ul>
+ * <li>A GET request against the cluster endpoint.</li>
+ * <li>A query against {@link HostRoleCommandDAO} to get a summary of request
+ * statuses</li>
+ * <ul>
+ */
+public class AmbariPerformanceRunnable extends AlertRunnable {
+
+  /**
+   * Logger.
+   */
+  private final static Logger LOG = LoggerFactory.getLogger(AmbariPerformanceRunnable.class);
+
+  /**
+   * <pre>
+   * Performance Overview:
+   *   Database Access (Request By Status): 330ms (OK)
+   *   REST API (Cluster Request): 5,456ms (WARNING)
+   * </pre>
+   */
+  private static final String PERFORMANCE_OVERVIEW_TEMPLATE = "Performance Overview:"
+      + System.lineSeparator() + "{0}";
+
+  /**
+   * Example: {@code Database Access (Request By Status): 330ms (OK)}
+   */
+  private static final String PERFORMANCE_AREA_TEMPLATE = "  {0}: {1}ms ({2})";
+
+  /**
+   * Example:
+   * {@code Unable to execute performance alert area REQUEST_BY_STATUS (UNKNOWN)}
+   */
+  private static final String PERFORMANCE_AREA_FAILURE_TEMPLATE = "  Unable to execute performance alert area {0}: ({1})";
+
+  /**
+   * Used for converting {@link AlertDefinitionEntity} into
+   * {@link AlertDefinition} instances.
+   */
+  @Inject
+  private AlertDefinitionFactory m_definitionFactory;
+
+  /**
+   * The {@link PerformanceArea} enumeration represents logical areas of
+   * functionality to test for performance.
+   */
+  private enum PerformanceArea {
+
+    /**
+     * Query for requests by {@link RequestStatus#IN_PROGRESS}.
+     */
+    REQUEST_BY_STATUS("Database Access (Request By Status)",
+        "request.by.status.warning.threshold", 3000, "request.by.status.critical.threshold", 5000) {
+      /**
+       * {@inheritDoc}
+       */
+      @Override
+      void execute(AmbariPerformanceRunnable runnable, Cluster cluster) throws Exception {
+        runnable.m_actionManager.get().getRequestsByStatus(RequestStatus.IN_PROGRESS,
+            BaseRequest.DEFAULT_PAGE_SIZE, false);
+      }
+    },
+
+    /**
+     * Query through the REST API framework for a cluster.
+     */
+    REST_API_GET_CLUSTER("REST API (Cluster)",
+        "rest.api.cluster.warning.threshold",
+        5000, "rest.api.cluster.critical.threshold", 7000) {
+      /**
+       * {@inheritDoc}
+       */
+      @Override
+      void execute(AmbariPerformanceRunnable runnable, Cluster cluster) throws Exception {
+        Type type = Type.Cluster;
+        ResourceProvider provider = AbstractControllerResourceProvider.getResourceProvider(
+            type, PropertyHelper.getPropertyIds(type), PropertyHelper.getKeyPropertyIds(type),
+            runnable.m_amc.get());
+
+        Set<String> propertyIds = new HashSet<String>();
+
+        propertyIds.add(ClusterResourceProvider.CLUSTER_ID_PROPERTY_ID);
+        propertyIds.add(ClusterResourceProvider.CLUSTER_NAME_PROPERTY_ID);
+
+        // create the request
+        Request request = PropertyHelper.getReadRequest(propertyIds);
+
+        // build the predicate for this cluster
+        Predicate predicate = new PredicateBuilder().property(
+            ClusterResourceProvider.CLUSTER_NAME_PROPERTY_ID).equals(
+                cluster.getClusterName()).toPredicate();
+
+        provider.getResources(request, predicate);
+      }
+    };
+
+    /**
+     * The label for the performance area.
+     */
+    private final String m_label;
+
+    /**
+     * The name of the parameter on the alert definition which represents the
+     * {@link AlertState#WARNING} threshold value.
+     */
+    private final String m_warningParameter;
+
+    /**
+     * A default {@link AlertState#WARNING} threshold value of the definition
+     * doesn't have {@link #m_warningParameter} defined.
+     */
+    private final int m_defaultWarningThreshold;
+
+    /**
+     * The name of the parameter on the alert definition which represents the
+     * {@link AlertState#CRITICAL} threshold value.
+     */
+    private final String m_criticalParameter;
+
+    /**
+     * A default {@link AlertState#WARNING} threshold value of the definition
+     * doesn't have {@link #m_criticalParameter} defined.
+     */
+    private final int m_defaultCriticalThreshold;
+
+    /**
+     * Constructor.
+     *
+     * @param label
+     *          the display label for this performance area (not {@code null}).
+     * @param warningParameter
+     *          the definition parameter name for the warning threshold (not
+     *          {@code null})
+     * @param defaultWarningThreshold
+     *          the default value to use if the definition does not have a
+     *          warning threshold paramter.
+     * @param criticalParameter
+     *          the definition parameter name for the critical threshold (not
+     *          {@code null})
+     * @param defaultCriticalThreshold
+     *          the default value to use if the definition does not have a
+     *          critical threshold paramter.
+     */
+    private PerformanceArea(String label, String warningParameter, int defaultWarningThreshold,
+        String criticalParameter, int defaultCriticalThreshold) {
+      m_label = label;
+      m_warningParameter = warningParameter;
+      m_defaultWarningThreshold = defaultWarningThreshold;
+      m_criticalParameter = criticalParameter;
+      m_defaultCriticalThreshold = defaultCriticalThreshold;
+    }
+
+    /**
+     * Runs the {@link PerformanceArea}.
+     *
+     * @param runnable
+     *          a reference to the parent {@link AlertRunnable} which has
+     *          injected members for use.
+     * @return a result of running the performance area (never {@code null}).
+     */
+    abstract void execute(AmbariPerformanceRunnable runnable, Cluster cluster) throws Exception;
+  }
+
+  /**
+   * Used for querying for requests by status.
+   */
+  @Inject
+  private Provider<ActionManager> m_actionManager;
+
+  @Inject
+  private Provider<AmbariManagementController> m_amc;
+
+  /**
+   * Constructor.
+   *
+   * @param definitionName
+   */
+  public AmbariPerformanceRunnable(String definitionName) {
+    super(definitionName);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  List<Alert> execute(Cluster cluster, AlertDefinitionEntity entity) throws AmbariException {
+    // coerce the entity into a business object so that the list of parameters
+    // can be extracted and used for threshold calculation
+    AlertDefinition definition = m_definitionFactory.coerce(entity);
+    ServerSource serverSource = (ServerSource) definition.getSource();
+    List<AlertParameter> parameters = serverSource.getParameters();
+    List<String> results = new ArrayList<>();
+
+    // start out assuming OK
+    AlertState alertState = AlertState.OK;
+
+    // run every performance area
+    for (PerformanceArea performanceArea : PerformanceArea.values()) {
+      // execute the performance area, creating an UNKNOWN state on exceptions
+      PerformanceResult performanceResult;
+      try {
+        long startTime = System.currentTimeMillis();
+        performanceArea.execute(this, cluster);
+        long totalTime = System.currentTimeMillis() - startTime;
+
+        performanceResult = calculatePerformanceResult(performanceArea, totalTime, parameters);
+
+      } catch (Exception exception) {
+        String result = MessageFormat.format(PERFORMANCE_AREA_FAILURE_TEMPLATE, performanceArea,
+            AlertState.UNKNOWN);
+
+        LOG.error(result, exception);
+        performanceResult = new PerformanceResult(result, AlertState.UNKNOWN);
+      }
+
+      String result = performanceResult.getResult();
+      AlertState resultAlertState = performanceResult.getAlertState();
+
+      // keep track of the string result for formatting later
+      results.add(result);
+
+      // keep track of the overall state of "this" alert
+      switch (resultAlertState) {
+        case CRITICAL:
+          alertState = AlertState.CRITICAL;
+          break;
+        case OK:
+          break;
+        case SKIPPED:
+          break;
+        case UNKNOWN:
+          if (alertState == AlertState.OK) {
+            alertState = AlertState.UNKNOWN;
+          }
+          break;
+        case WARNING:
+          if (alertState != AlertState.CRITICAL) {
+            alertState = AlertState.WARNING;
+          }
+          break;
+        default:
+          break;
+      }
+    }
+
+    // create a text overview of all of the runs
+    String allResults = StringUtils.join(results, System.lineSeparator());
+    String overview = MessageFormat.format(PERFORMANCE_OVERVIEW_TEMPLATE, allResults);
+
+    // build the alert to return
+    Alert alert = new Alert(entity.getDefinitionName(), null, entity.getServiceName(),
+        entity.getComponentName(), null, alertState);
+
+    alert.setLabel(entity.getLabel());
+    alert.setText(overview);
+    alert.setTimestamp(System.currentTimeMillis());
+    alert.setCluster(cluster.getClusterName());
+
+    return Collections.singletonList(alert);
+  }
+
+  /**
+   * Calculates the state based on the threshold values for a
+   * {@link PerformanceArea} and an actual run time.
+   *
+   * @param area
+   *          the area to calculate the result for (not {@code null}).
+   * @param time
+   *          the time taken, in milliseconds, to run the test.
+   * @param parameters
+   *          a list of parameters from the alert definition which contain the
+   *          threshold values.
+   * @return a result of running the performance area (never {@code null}).
+   */
+  PerformanceResult calculatePerformanceResult(PerformanceArea area, long time,
+      List<AlertParameter> parameters) {
+    AlertState alertState = AlertState.OK;
+    int warningThreshold = area.m_defaultWarningThreshold;
+    int criticalThreshold = area.m_defaultCriticalThreshold;
+
+    for (AlertParameter parameter : parameters) {
+      Object value = parameter.getValue();
+
+      if (StringUtils.equals(parameter.getName(), area.m_warningParameter)) {
+        warningThreshold = getThresholdValue(value, warningThreshold);
+      }
+
+      if (StringUtils.equals(parameter.getName(), area.m_criticalParameter)) {
+        criticalThreshold = getThresholdValue(value, criticalThreshold);
+      }
+    }
+
+    if (time >= warningThreshold && time < criticalThreshold) {
+      alertState = AlertState.WARNING;
+    }
+
+    if (time >= criticalThreshold) {
+      alertState = AlertState.WARNING;
+    }
+
+    String resultLabel = MessageFormat.format(PERFORMANCE_AREA_TEMPLATE, area.m_label, time,
+        alertState);
+
+    return new PerformanceResult(resultLabel, alertState);
+  }
+
+  /**
+   * Converts the given value to an integer safely.
+   *
+   * @param value
+   * @param defaultValue
+   * @return
+   */
+  int getThresholdValue(Object value, int defaultValue) {
+    if (null == value) {
+      return defaultValue;
+    }
+
+    if (value instanceof Number) {
+      return ((Number) value).intValue();
+    }
+
+    if (!(value instanceof String)) {
+      value = value.toString();
+    }
+
+    if (!NumberUtils.isNumber((String) value)) {
+      return defaultValue;
+    }
+
+    Number number = NumberUtils.createNumber((String) value);
+    return number.intValue();
+  }
+
+  /**
+   * The {@link PerformanceResult} class is used to wrap the result of a
+   * {@link PerformanceArea}.
+   */
+  private static final class PerformanceResult {
+    private final String m_result;
+    private final AlertState m_alertState;
+
+    /**
+     * Constructor.
+     *
+     * @param result
+     *          the text of the result (not {@code null}).
+     * @param alertState
+     *          the result state (not {@code null}).
+     */
+    private PerformanceResult(String result, AlertState alertState) {
+      m_result = result;
+      m_alertState = alertState;
+    }
+
+    /**
+     * Gets the fully-rendered result text, such as:
+     * {@code Database Access (Request By Status): 330ms (OK)}
+     *
+     * @return the result
+     */
+    public String getResult() {
+      return m_result;
+    }
+
+    /**
+     * The state of the result as calculated by the threshold parameters.
+     *
+     * @return the state
+     */
+    public AlertState getAlertState() {
+      return m_alertState;
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/28f1a958/ambari-server/src/main/java/org/apache/ambari/server/alerts/StaleAlertRunnable.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/alerts/StaleAlertRunnable.java b/ambari-server/src/main/java/org/apache/ambari/server/alerts/StaleAlertRunnable.java
index 54abd62..cf12bbf 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/alerts/StaleAlertRunnable.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/alerts/StaleAlertRunnable.java
@@ -18,16 +18,13 @@
 package org.apache.ambari.server.alerts;
 
 import java.text.MessageFormat;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.TreeSet;
 
-import org.apache.ambari.server.events.AlertEvent;
-import org.apache.ambari.server.events.AlertReceivedEvent;
-import org.apache.ambari.server.events.publishers.AlertEventPublisher;
-import org.apache.ambari.server.orm.dao.AlertDefinitionDAO;
 import org.apache.ambari.server.orm.dao.AlertsDAO;
 import org.apache.ambari.server.orm.entities.AlertCurrentEntity;
 import org.apache.ambari.server.orm.entities.AlertDefinitionEntity;
@@ -35,16 +32,12 @@ import org.apache.ambari.server.orm.entities.AlertHistoryEntity;
 import org.apache.ambari.server.state.Alert;
 import org.apache.ambari.server.state.AlertState;
 import org.apache.ambari.server.state.Cluster;
-import org.apache.ambari.server.state.Clusters;
 import org.apache.ambari.server.state.MaintenanceState;
 import org.apache.ambari.server.state.alert.SourceType;
 import org.apache.ambari.server.state.services.AmbariServerAlertService;
 import org.apache.commons.lang.StringUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import com.google.inject.Inject;
-import com.google.inject.Provider;
 
 /**
  * The {@link StaleAlertRunnable} is used by the
@@ -53,18 +46,7 @@ import com.google.inject.Provider;
  * single alert with {@link AlertState#CRITICAL} along with a textual
  * description of the alerts that are stale.
  */
-public class StaleAlertRunnable implements Runnable {
-
-  /**
-   * Logger.
-   */
-  private final static Logger LOG = LoggerFactory.getLogger(StaleAlertRunnable.class);
-
-  /**
-   * The unique name for the alert definition that governs this service.
-   */
-  private static final String STALE_ALERT_DEFINITION_NAME = "ambari_server_stale_alerts";
-
+public class StaleAlertRunnable extends AlertRunnable {
   /**
    * The message for the alert when all services have run in their designated
    * intervals.
@@ -85,6 +67,10 @@ public class StaleAlertRunnable implements Runnable {
    */
   private static final long MINUTE_TO_MS_CONVERSION = 60L * 1000L;
 
+  private static final long MILLISECONDS_PER_MINUTE = 1000L * 60L;
+  private static final int MINUTES_PER_DAY = 24 * 60;
+  private static final int MINUTES_PER_HOUR = 60;
+
   /**
    * Used to get the current alerts and the last time they ran.
    */
@@ -92,154 +78,113 @@ public class StaleAlertRunnable implements Runnable {
   private AlertsDAO m_alertsDao;
 
   /**
-   * Used to get alert definitions to use when generating alert instances.
-   */
-  @Inject
-  private Provider<Clusters> m_clustersProvider;
-
-  /**
-   * Used for looking up alert definitions.
-   */
-  @Inject
-  private AlertDefinitionDAO m_dao;
-
-  /**
-   * Publishes {@link AlertEvent} instances.
+   * Constructor.
+   *
+   * @param definitionName
    */
-  @Inject
-  private AlertEventPublisher m_alertEventPublisher;
-
-  /**
-   * Constructor. Required for type introspection by
-   * {@link AmbariServerAlertService}.
-   */
-  public StaleAlertRunnable() {
+  public StaleAlertRunnable(String definitionName) {
+    super(definitionName);
   }
 
   /**
    * {@inheritDoc}
    */
   @Override
-  public void run() {
-    try {
-      Map<String, Cluster> clusterMap = m_clustersProvider.get().getClusters();
-      for (Cluster cluster : clusterMap.values()) {
-        AlertDefinitionEntity entity = m_dao.findByName(cluster.getClusterId(),
-            STALE_ALERT_DEFINITION_NAME);
-
-        // skip this cluster if the runnable's alert definition is missing or
-        // disabled
-        if (null == entity || !entity.getEnabled()) {
-          continue;
-        }
+  List<Alert> execute(Cluster cluster, AlertDefinitionEntity myDefinition) {
+    Set<String> staleAlerts = new TreeSet<String>();
+    Map<String, Set<String>> staleHostAlerts = new HashMap<>();
+    Set<String> hostsWithStaleAlerts = new TreeSet<>();
 
-        long now = System.currentTimeMillis();
-        Set<String> staleAlerts = new TreeSet<String>();
-        Map<String, Set<String>> staleHostAlerts = new HashMap<>();
-        Set<String> hostsWithStaleAlerts = new TreeSet<>();
+    // get the cluster's current alerts
+    List<AlertCurrentEntity> currentAlerts = m_alertsDao.findCurrentByCluster(
+        cluster.getClusterId());
 
-        // get the cluster's current alerts
-        List<AlertCurrentEntity> currentAlerts = m_alertsDao.findCurrentByCluster(cluster.getClusterId());
+    long now = System.currentTimeMillis();
 
-        // for each current alert, check to see if the last time it ran is
-        // more than 2x its interval value (indicating it hasn't run)
-        for (AlertCurrentEntity current : currentAlerts) {
-          AlertHistoryEntity history = current.getAlertHistory();
-          AlertDefinitionEntity definition = history.getAlertDefinition();
+    // for each current alert, check to see if the last time it ran is
+    // more than 2x its interval value (indicating it hasn't run)
+    for (AlertCurrentEntity current : currentAlerts) {
+      AlertHistoryEntity history = current.getAlertHistory();
+      AlertDefinitionEntity currentDefinition = history.getAlertDefinition();
 
-          // skip aggregates as they are special
-          if (definition.getSourceType() == SourceType.AGGREGATE) {
-            continue;
-          }
+      // skip aggregates as they are special
+      if (currentDefinition.getSourceType() == SourceType.AGGREGATE) {
+          continue;
+        }
 
-          // skip alerts in maintenance mode
-          if (current.getMaintenanceState() != MaintenanceState.OFF) {
-            continue;
-          }
+      // skip alerts in maintenance mode
+      if (current.getMaintenanceState() != MaintenanceState.OFF) {
+        continue;
+      }
 
-          // skip alerts that have not run yet
-          if (current.getLatestTimestamp() == 0) {
-            continue;
-          }
+      // skip alerts that have not run yet
+      if (current.getLatestTimestamp() == 0) {
+        continue;
+      }
 
-          // skip this alert (who watches the watchers)
-          if (definition.getDefinitionName().equals(STALE_ALERT_DEFINITION_NAME)) {
-            continue;
-          }
+      // skip this alert (who watches the watchers)
+      if (currentDefinition.getDefinitionName().equals(m_definitionName)) {
+        continue;
+      }
 
-          // convert minutes to milliseconds for the definition's interval
-          long intervalInMillis = definition.getScheduleInterval()
-              * MINUTE_TO_MS_CONVERSION;
+      // convert minutes to milliseconds for the definition's interval
+      long intervalInMillis = currentDefinition.getScheduleInterval() * MINUTE_TO_MS_CONVERSION;
 
-          // if the last time it was run is >= 2x the interval, it's stale
-          long timeDifference = now - current.getLatestTimestamp();
-          if (timeDifference >= 2 * intervalInMillis) {
+      // if the last time it was run is >= 2x the interval, it's stale
+      long timeDifference = now - current.getLatestTimestamp();
+      if (timeDifference >= 2 * intervalInMillis) {
 
-            // it is technically possible to have a null/blank label; if so,
-            // default to the name of the definition
-            String label = definition.getLabel();
-            if (StringUtils.isEmpty(label)) {
-              label = definition.getDefinitionName();
-            }
+        // it is technically possible to have a null/blank label; if so,
+        // default to the name of the definition
+        String label = currentDefinition.getLabel();
+        if (StringUtils.isEmpty(label)) {
+          label = currentDefinition.getDefinitionName();
+          }
 
-            if (null != history.getHostName()) {
-              // keep track of the host, if not null
-              String hostName = history.getHostName();
-              hostsWithStaleAlerts.add(hostName);
-              if(!staleHostAlerts.containsKey(hostName)) {
-                staleHostAlerts.put(hostName, new TreeSet<String>());
-              }
-
-              staleHostAlerts.get(hostName).add(MessageFormat.format(TIMED_LABEL_MSG, label,
-                  millisToHumanReadableStr(timeDifference)));
-            } else {
-              // non host alerts
-              staleAlerts.add(label);
+        if (null != history.getHostName()) {
+          // keep track of the host, if not null
+          String hostName = history.getHostName();
+          hostsWithStaleAlerts.add(hostName);
+          if (!staleHostAlerts.containsKey(hostName)) {
+            staleHostAlerts.put(hostName, new TreeSet<String>());
             }
-          }
-        }
 
-        for(String host : staleHostAlerts.keySet()) {
-          staleAlerts.add(MessageFormat.format(HOST_LABEL_MSG, host,
-              StringUtils.join(staleHostAlerts.get(host), ",\n  ")));
+          staleHostAlerts.get(hostName).add(MessageFormat.format(TIMED_LABEL_MSG, label,
+              millisToHumanReadableStr(timeDifference)));
+        } else {
+          // non host alerts
+          staleAlerts.add(label);
+          }
         }
+    }
 
-        AlertState alertState = AlertState.OK;
-        String alertText = ALL_ALERTS_CURRENT_MSG;
+    for (String host : staleHostAlerts.keySet()) {
+      staleAlerts.add(MessageFormat.format(HOST_LABEL_MSG, host,
+          StringUtils.join(staleHostAlerts.get(host), ",\n  ")));
+    }
 
-        // if there are stale alerts, mark as CRITICAL with the list of
-        // alerts
-        if( !staleAlerts.isEmpty() ){
-          alertState = AlertState.CRITICAL;
-          alertText = MessageFormat.format(STALE_ALERTS_MSG,
-              staleAlerts.size(), hostsWithStaleAlerts.size(),
-              StringUtils.join(staleAlerts, ",\n"));
-        }
+    AlertState alertState = AlertState.OK;
+    String alertText = ALL_ALERTS_CURRENT_MSG;
 
-        Alert alert = new Alert(entity.getDefinitionName(), null,
-            entity.getServiceName(), entity.getComponentName(), null,
-            alertState);
+    // if there are stale alerts, mark as CRITICAL with the list of
+    // alerts
+    if (!staleAlerts.isEmpty()) {
+      alertState = AlertState.CRITICAL;
+      alertText = MessageFormat.format(STALE_ALERTS_MSG, staleAlerts.size(),
+          hostsWithStaleAlerts.size(), StringUtils.join(staleAlerts, ",\n"));
+    }
 
-        alert.setLabel(entity.getLabel());
-        alert.setText(alertText);
-        alert.setTimestamp(now);
-        alert.setCluster(cluster.getClusterName());
+    Alert alert = new Alert(myDefinition.getDefinitionName(), null, myDefinition.getServiceName(),
+        myDefinition.getComponentName(), null, alertState);
 
-        AlertReceivedEvent event = new AlertReceivedEvent(
-            cluster.getClusterId(), alert);
+    alert.setLabel(myDefinition.getLabel());
+    alert.setText(alertText);
+    alert.setTimestamp(now);
+    alert.setCluster(cluster.getClusterName());
 
-        m_alertEventPublisher.publish(event);
-      }
-    } catch (Exception exception) {
-      LOG.error("Unable to run the {} alert", STALE_ALERT_DEFINITION_NAME,
-          exception);
-    }
+    return Collections.singletonList(alert);
   }
 
-  private static final long MILLISECONDS_PER_MINUTE = 1000l * 60l;
-  private static final int MINUTES_PER_DAY = 24 * 60;
-  private static final int MINUTES_PER_HOUR = 60;
-
   /**
    * Converts given {@code milliseconds} to human-readable {@link String} like "1d 2h 3m" or "2h 4m".
    * @param milliseconds milliseconds to convert

http://git-wip-us.apache.org/repos/asf/ambari/blob/28f1a958/ambari-server/src/main/java/org/apache/ambari/server/state/alert/ParameterizedSource.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/alert/ParameterizedSource.java b/ambari-server/src/main/java/org/apache/ambari/server/state/alert/ParameterizedSource.java
new file mode 100644
index 0000000..ed5ab5f
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/alert/ParameterizedSource.java
@@ -0,0 +1,264 @@
+/**
+ * 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.ambari.server.state.alert;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.ambari.server.state.AlertState;
+
+import com.google.gson.annotations.SerializedName;
+
+
+/**
+ * The {@link ParameterizedSource} is used for alerts where the logic of
+ * computing the {@link AlertState} is dependant on user-specified parameters.
+ * For example, the parameters might be threshold values.
+ */
+public abstract class ParameterizedSource extends Source {
+
+  /**
+   * A list of all of the alert parameters, if any.
+   */
+  @SerializedName("parameters")
+  List<AlertParameter> m_parameters;
+
+  /**
+   * Gets a list of the optional parameters which govern how a parameterized
+   * alert behaves. These are usually threshold values.
+   *
+   * @return the list of parameters, or an empty list if none.
+   */
+  public List<AlertParameter> getParameters() {
+    if (null == m_parameters) {
+      return Collections.emptyList();
+    }
+
+    return m_parameters;
+  }
+
+  /**
+   * The {@link AlertParameter} class represents a single parameter that can be
+   * passed into an alert which takes parameters.
+   */
+  public static class AlertParameter {
+    @SerializedName("name")
+    private String m_name;
+
+    @SerializedName("display_name")
+    private String m_displayName;
+
+    @SerializedName("units")
+    private String m_units;
+
+    @SerializedName("value")
+    private Object m_value;
+
+    @SerializedName("description")
+    private String m_description;
+
+    @SerializedName("type")
+    private AlertParameterType m_type;
+
+    @SerializedName("visibility")
+    private AlertParameterVisibility m_visibility;
+
+    /**
+     * If this alert parameter controls a threshold, then its specified here,
+     * otherwise it's {@code null}.
+     */
+    @SerializedName("threshold")
+    private AlertState m_threshold;
+
+    /**
+     * Gets the unique name of the parameter.
+     *
+     * @return the name
+     */
+    public String getName() {
+      return m_name;
+    }
+
+    /**
+     * Gets the human readable name of the parameter.
+     *
+     * @return the displayName
+     */
+    public String getDisplayName() {
+      return m_displayName;
+    }
+
+    /**
+     * Gets the display units of the paramter.
+     *
+     * @return the units
+     */
+    public String getUnits() {
+      return m_units;
+    }
+
+    /**
+     * Gets the value of the parameter.
+     *
+     * @return the value
+     */
+    public Object getValue() {
+      return m_value;
+    }
+
+    /**
+     * Gets the description of the parameter.
+     *
+     * @return the description
+     */
+    public String getDescription() {
+      return m_description;
+    }
+
+    /**
+     * Gets the visibility of the parameter.
+     *
+     * @return the visibility
+     */
+    public AlertParameterVisibility getVisibility() {
+      return m_visibility;
+    }
+
+    /**
+     * Gets the threshold that this parameter directly controls, or {@code null}
+     * for none.
+     *
+     * @return the threshold, or {@code null}.
+     */
+    public AlertState getThreshold() {
+      return m_threshold;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public int hashCode() {
+      final int prime = 31;
+      int result = 1;
+      result = prime * result + ((m_description == null) ? 0 : m_description.hashCode());
+      result = prime * result + ((m_displayName == null) ? 0 : m_displayName.hashCode());
+      result = prime * result + ((m_name == null) ? 0 : m_name.hashCode());
+      result = prime * result + ((m_threshold == null) ? 0 : m_threshold.hashCode());
+      result = prime * result + ((m_type == null) ? 0 : m_type.hashCode());
+      result = prime * result + ((m_units == null) ? 0 : m_units.hashCode());
+      result = prime * result + ((m_value == null) ? 0 : m_value.hashCode());
+      result = prime * result + ((m_visibility == null) ? 0 : m_visibility.hashCode());
+      return result;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public boolean equals(Object obj) {
+      if (this == obj) {
+        return true;
+      }
+      if (obj == null) {
+        return false;
+      }
+      if (getClass() != obj.getClass()) {
+        return false;
+      }
+      AlertParameter other = (AlertParameter) obj;
+      if (m_description == null) {
+        if (other.m_description != null) {
+          return false;
+        }
+      } else if (!m_description.equals(other.m_description)) {
+        return false;
+      }
+      if (m_displayName == null) {
+        if (other.m_displayName != null) {
+          return false;
+        }
+      } else if (!m_displayName.equals(other.m_displayName)) {
+        return false;
+      }
+      if (m_name == null) {
+        if (other.m_name != null) {
+          return false;
+        }
+      } else if (!m_name.equals(other.m_name)) {
+        return false;
+      }
+      if (m_threshold != other.m_threshold) {
+        return false;
+      }
+      if (m_type != other.m_type) {
+        return false;
+      }
+      if (m_units == null) {
+        if (other.m_units != null) {
+          return false;
+        }
+      } else if (!m_units.equals(other.m_units)) {
+        return false;
+      }
+      if (m_value == null) {
+        if (other.m_value != null) {
+          return false;
+        }
+      } else if (!m_value.equals(other.m_value)) {
+        return false;
+      }
+      if (m_visibility == null) {
+        if (other.m_visibility != null) {
+          return false;
+        }
+      } else if (!m_visibility.equals(other.m_visibility)) {
+        return false;
+      }
+      return true;
+    }
+  }
+
+  /**
+   * The {@link AlertParameterType} enum represents the value type.
+   */
+  public enum AlertParameterType {
+    /**
+     * String
+     */
+    STRING,
+
+    /**
+     * Integers, longs, floats, etc.
+     */
+    NUMERIC,
+
+    /**
+     * A percent value, expessed as a float.
+     */
+    PERCENT
+  }
+
+  /**
+   * The {@link AlertParameterVisibility} enum represents the visibility of
+   * alert parameters.
+   */
+  public enum AlertParameterVisibility {
+    VISIBLE, HIDDEN, READ_ONLY
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/28f1a958/ambari-server/src/main/java/org/apache/ambari/server/state/alert/ScriptSource.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/alert/ScriptSource.java b/ambari-server/src/main/java/org/apache/ambari/server/state/alert/ScriptSource.java
index 5871178..d1b7070 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/alert/ScriptSource.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/alert/ScriptSource.java
@@ -17,10 +17,6 @@
  */
 package org.apache.ambari.server.state.alert;
 
-import java.util.List;
-
-import org.apache.ambari.server.state.AlertState;
-
 import com.google.gson.annotations.SerializedName;
 
 /**
@@ -29,18 +25,12 @@ import com.google.gson.annotations.SerializedName;
  * Equality checking for instances of this class should be executed on every
  * member to ensure that reconciling stack differences is correct.
  */
-public class ScriptSource extends Source {
+public class ScriptSource extends ParameterizedSource {
 
   @SerializedName("path")
   private String m_path = null;
 
   /**
-   * A list of all of the script parameters, if any.
-   */
-  @SerializedName("parameters")
-  private List<ScriptParameter> m_parameters;
-
-  /**
    * @return the path to the script file.
    */
   public String getPath() {
@@ -88,216 +78,4 @@ public class ScriptSource extends Source {
 
     return true;
   }
-
-  /**
-   * The {@link ScriptParameter} class represents a single parameter that can be
-   * passed into a script alert.
-   */
-  public static class ScriptParameter {
-    @SerializedName("name")
-    private String m_name;
-
-    @SerializedName("display_name")
-    private String m_displayName;
-
-    @SerializedName("units")
-    private String m_units;
-
-    @SerializedName("value")
-    private Object m_value;
-
-    @SerializedName("description")
-    private String m_description;
-
-    @SerializedName("type")
-    private ScriptParameterType m_type;
-
-
-    @SerializedName("visibility")
-    private ScriptParameterVisibility m_visibility;
-
-    /**
-     * If this script parameter controls a threshold, then its specified here,
-     * otherwise it's {@code null}.
-     */
-    @SerializedName("threshold")
-    private AlertState m_threshold;
-
-    /**
-     * Gets the unique name of the parameter.
-     *
-     * @return the name
-     */
-    public String getName() {
-      return m_name;
-    }
-
-    /**
-     * Gets the human readable name of the parameter.
-     *
-     * @return the displayName
-     */
-    public String getDisplayName() {
-      return m_displayName;
-    }
-
-    /**
-     * Gets the display units of the paramter.
-     *
-     * @return the units
-     */
-    public String getUnits() {
-      return m_units;
-    }
-
-    /**
-     * Gets the value of the parameter.
-     *
-     * @return the value
-     */
-    public Object getValue() {
-      return m_value;
-    }
-
-    /**
-     * Gets the description of the parameter.
-     *
-     * @return the description
-     */
-    public String getDescription() {
-      return m_description;
-    }
-
-    /**
-     * Gets the visibility of the parameter.
-     * @return the visibility
-     */
-    public ScriptParameterVisibility getVisibility() {
-      return m_visibility;
-    }
-
-    /**
-     * Gets the threshold that this parameter directly controls, or {@code null}
-     * for none.
-     *
-     * @return the threshold, or {@code null}.
-     */
-    public AlertState getThreshold() {
-      return m_threshold;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public int hashCode() {
-      final int prime = 31;
-      int result = 1;
-      result = prime * result + ((m_description == null) ? 0 : m_description.hashCode());
-      result = prime * result + ((m_displayName == null) ? 0 : m_displayName.hashCode());
-      result = prime * result + ((m_name == null) ? 0 : m_name.hashCode());
-      result = prime * result + ((m_threshold == null) ? 0 : m_threshold.hashCode());
-      result = prime * result + ((m_type == null) ? 0 : m_type.hashCode());
-      result = prime * result + ((m_units == null) ? 0 : m_units.hashCode());
-      result = prime * result + ((m_value == null) ? 0 : m_value.hashCode());
-      result = prime * result + ((m_visibility == null) ? 0 : m_visibility.hashCode());
-      return result;
-    }
-
-    /**
-     * {@inheritDoc}
-     */
-    @Override
-    public boolean equals(Object obj) {
-      if (this == obj) {
-        return true;
-      }
-      if (obj == null) {
-        return false;
-      }
-      if (getClass() != obj.getClass()) {
-        return false;
-      }
-      ScriptParameter other = (ScriptParameter) obj;
-      if (m_description == null) {
-        if (other.m_description != null) {
-          return false;
-        }
-      } else if (!m_description.equals(other.m_description)) {
-        return false;
-      }
-      if (m_displayName == null) {
-        if (other.m_displayName != null) {
-          return false;
-        }
-      } else if (!m_displayName.equals(other.m_displayName)) {
-        return false;
-      }
-      if (m_name == null) {
-        if (other.m_name != null) {
-          return false;
-        }
-      } else if (!m_name.equals(other.m_name)) {
-        return false;
-      }
-      if (m_threshold != other.m_threshold) {
-        return false;
-      }
-      if (m_type != other.m_type) {
-        return false;
-      }
-      if (m_units == null) {
-        if (other.m_units != null) {
-          return false;
-        }
-      } else if (!m_units.equals(other.m_units)) {
-        return false;
-      }
-      if (m_value == null) {
-        if (other.m_value != null) {
-          return false;
-        }
-      } else if (!m_value.equals(other.m_value)) {
-        return false;
-      }
-      if (m_visibility == null) {
-        if (other.m_visibility != null) {
-          return false;
-        }
-      } else if (!m_visibility.equals(other.m_visibility)) {
-        return false;
-      }
-      return true;
-    }
-
-
-    /**
-     * The {@link ScriptParameterType} enum represents the value type.
-     */
-    public enum ScriptParameterType {
-      /**
-       * String
-       */
-      STRING,
-
-      /**
-       * Integers, longs, floats, etc.
-       */
-      NUMERIC,
-
-      /**
-       * A percent value, expessed as a float.
-       */
-      PERCENT
-    }
-
-    /**
-     * The {@link ScriptParameterVisibility} enum represents the visibility of script parameter.
-     */
-    public enum ScriptParameterVisibility {
-      VISIBLE,
-      HIDDEN,
-      READ_ONLY
-    }
-  }
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/28f1a958/ambari-server/src/main/java/org/apache/ambari/server/state/alert/ServerSource.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/alert/ServerSource.java b/ambari-server/src/main/java/org/apache/ambari/server/state/alert/ServerSource.java
index 30e73bc..d5a69ab 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/alert/ServerSource.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/alert/ServerSource.java
@@ -23,7 +23,7 @@ import com.google.gson.annotations.SerializedName;
 /**
  * Alert when the source type is defined as {@link SourceType#SERVER}
  */
-public class ServerSource extends Source {
+public class ServerSource extends ParameterizedSource {
 
   @SerializedName("class")
   private String m_class;

http://git-wip-us.apache.org/repos/asf/ambari/blob/28f1a958/ambari-server/src/main/java/org/apache/ambari/server/state/services/AmbariServerAlertService.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/services/AmbariServerAlertService.java b/ambari-server/src/main/java/org/apache/ambari/server/state/services/AmbariServerAlertService.java
index a13a8c7..85fd92a 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/services/AmbariServerAlertService.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/services/AmbariServerAlertService.java
@@ -17,6 +17,8 @@
  */
 package org.apache.ambari.server.state.services;
 
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
@@ -26,6 +28,7 @@ import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
 
 import org.apache.ambari.server.AmbariService;
+import org.apache.ambari.server.alerts.AlertRunnable;
 import org.apache.ambari.server.controller.RootServiceResponseFactory.Components;
 import org.apache.ambari.server.controller.RootServiceResponseFactory.Services;
 import org.apache.ambari.server.orm.dao.AlertDefinitionDAO;
@@ -46,7 +49,9 @@ import com.google.inject.Provider;
 
 /**
  * The {@link AmbariServerAlertService} is used to manage the dynamically loaded
- * {@link Runnable}s which perform server-side alert checks.
+ * {@link AlertRunnable}s which perform server-side alert checks. The
+ * {@link AlertRunnable}s are scheduled using a {@link ScheduledExecutorService}
+ * with a fixed thread pool size.
  */
 @AmbariService
 public class AmbariServerAlertService extends AbstractScheduledService {
@@ -235,20 +240,24 @@ public class AmbariServerAlertService extends AbstractScheduledService {
 
     try {
       Class<?> clazz = Class.forName(sourceClass);
-      if (!Runnable.class.isAssignableFrom(clazz)) {
+      if (!AlertRunnable.class.isAssignableFrom(clazz)) {
         LOG.warn(
-            "Unable to schedule a server side alert for {} because it is not a Runnable",
-            sourceClass);
+            "Unable to schedule a server side alert for {} because it is not an {}", sourceClass,
+            AlertRunnable.class);
+
         return;
       }
 
       // instantiate and inject
-      Runnable runnable = (Runnable) clazz.newInstance();
-      m_injector.injectMembers(runnable);
+      Constructor<? extends AlertRunnable> constructor = clazz.asSubclass(
+          AlertRunnable.class).getConstructor(String.class);
+
+      AlertRunnable alertRunnable = constructor.newInstance(entity.getDefinitionName());
+      m_injector.injectMembers(alertRunnable);
 
       // schedule the runnable alert
       ScheduledFuture<?> scheduledFuture = m_scheduledExecutorService.scheduleWithFixedDelay(
-          runnable, interval, interval, TimeUnit.MINUTES);
+          alertRunnable, interval, interval, TimeUnit.MINUTES);
 
       String definitionName = entity.getDefinitionName();
       ScheduledAlert scheduledAlert = new ScheduledAlert(scheduledFuture, interval);
@@ -258,9 +267,17 @@ public class AmbariServerAlertService extends AbstractScheduledService {
           definitionName, interval);
 
     } catch (ClassNotFoundException cnfe) {
-      LOG.warn(
+      LOG.error(
           "Unable to schedule a server side alert for {} because it could not be found in the classpath",
           sourceClass);
+    } catch (NoSuchMethodException nsme) {
+      LOG.error(
+          "Unable to schedule a server side alert for {} because it does not have a constructor which takes the proper arguments.",
+          sourceClass);
+    } catch (InvocationTargetException ite) {
+      LOG.error(
+          "Unable to schedule a server side alert for {} because an exception occurred while constructing the instance.",
+          sourceClass, ite);
     }
   }
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/28f1a958/ambari-server/src/main/resources/alerts.json
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/alerts.json b/ambari-server/src/main/resources/alerts.json
index 81f1f99..27ae76d 100644
--- a/ambari-server/src/main/resources/alerts.json
+++ b/ambari-server/src/main/resources/alerts.json
@@ -26,6 +26,56 @@
           "type": "SERVER",
           "class": "org.apache.ambari.server.alerts.StaleAlertRunnable"
         }
+      },
+      {
+        "name": "ambari_server_performance",
+        "label": "Ambari Server Performance",
+        "description": "This alert is triggered if the server detects that there is a potential performance problem with Ambari. This type of issue can arise for many reasons, but is typically attributed to slow database queries and host resource exhaustion.",
+        "interval": 5,
+        "scope": "SERVICE",
+        "enabled": true,
+        "source": {
+          "type": "SERVER",
+          "class": "org.apache.ambari.server.alerts.AmbariPerformanceRunnable",
+          "parameters": [
+            {
+              "name": "request.by.status.warning.threshold",
+              "display_name": "Warning",
+              "value": 3000,
+              "type": "NUMERIC",
+              "description": "The time to find requests in progress before a warning alert is triggered.",
+              "units": "ms",
+              "threshold": "WARNING"
+            },
+            {
+              "name": "request.by.status.critical.threshold",
+              "display_name": "Critical",
+              "value": 5000,
+              "type": "NUMERIC",
+              "description": "The time to find requests in progress before a critical alert is triggered.",
+              "units": "ms",
+              "threshold": "CRITICAL"
+            },
+            {
+              "name": "rest.api.cluster.warning.threshold",
+              "display_name": "Warning",
+              "value": 5000,
+              "type": "NUMERIC",
+              "description": "The time to get a cluster via the REST API before a warning alert is triggered.",
+              "units": "ms",
+              "threshold": "WARNING"
+            },
+            {
+              "name": "rest.api.cluster.critical.threshold",
+              "display_name": "Critical",
+              "value": 7000,
+              "type": "NUMERIC",
+              "description": "The time to get a cluster via the REST API before a critical alert is triggered.",
+              "units": "ms",
+              "threshold": "CRITICAL"
+            }
+          ]
+        }
       }
     ],
     "AMBARI_AGENT" : [

http://git-wip-us.apache.org/repos/asf/ambari/blob/28f1a958/ambari-server/src/test/java/org/apache/ambari/server/alerts/AgentHeartbeatAlertRunnableTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/alerts/AgentHeartbeatAlertRunnableTest.java b/ambari-server/src/test/java/org/apache/ambari/server/alerts/AgentHeartbeatAlertRunnableTest.java
index 673db7a..377ba30 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/alerts/AgentHeartbeatAlertRunnableTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/alerts/AgentHeartbeatAlertRunnableTest.java
@@ -25,6 +25,7 @@ import static org.easymock.EasyMock.replay;
 import static org.easymock.EasyMock.verify;
 
 import java.lang.reflect.Field;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -125,10 +126,10 @@ public class AgentHeartbeatAlertRunnableTest {
     // mock the cluster
     expect(m_cluster.getClusterId()).andReturn(CLUSTER_ID).atLeastOnce();
     expect(m_cluster.getClusterName()).andReturn(CLUSTER_NAME).atLeastOnce();
+    expect(m_cluster.getHosts()).andReturn(Collections.singleton(m_host)).atLeastOnce();
 
     // mock clusters
     expect(m_clusters.getClusters()).andReturn(clusterMap).atLeastOnce();
-    expect(m_clusters.getHostsForCluster(CLUSTER_NAME)).andReturn(hostMap).atLeastOnce();
 
     // mock the definition DAO
     expect(m_definitionDao.findByName(CLUSTER_ID, DEFINITION_NAME)).andReturn(
@@ -152,7 +153,9 @@ public class AgentHeartbeatAlertRunnableTest {
         m_listener.getAlertEventReceivedCount(AlertReceivedEvent.class));
 
     // instantiate and inject mocks
-    AgentHeartbeatAlertRunnable runnable = new AgentHeartbeatAlertRunnable();
+    AgentHeartbeatAlertRunnable runnable = new AgentHeartbeatAlertRunnable(
+        m_definition.getDefinitionName());
+
     m_injector.injectMembers(runnable);
 
     // run the alert
@@ -186,7 +189,9 @@ public class AgentHeartbeatAlertRunnableTest {
         m_listener.getAlertEventReceivedCount(AlertReceivedEvent.class));
 
     // instantiate and inject mocks
-    AgentHeartbeatAlertRunnable runnable = new AgentHeartbeatAlertRunnable();
+    AgentHeartbeatAlertRunnable runnable = new AgentHeartbeatAlertRunnable(
+        m_definition.getDefinitionName());
+
     m_injector.injectMembers(runnable);
 
     // run the alert

http://git-wip-us.apache.org/repos/asf/ambari/blob/28f1a958/ambari-server/src/test/java/org/apache/ambari/server/alerts/AmbariPerformanceRunnableTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/alerts/AmbariPerformanceRunnableTest.java b/ambari-server/src/test/java/org/apache/ambari/server/alerts/AmbariPerformanceRunnableTest.java
new file mode 100644
index 0000000..31ed745
--- /dev/null
+++ b/ambari-server/src/test/java/org/apache/ambari/server/alerts/AmbariPerformanceRunnableTest.java
@@ -0,0 +1,232 @@
+/**
+ * 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.ambari.server.alerts;
+
+import static junit.framework.Assert.assertEquals;
+import static org.easymock.EasyMock.createNiceMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.verify;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.persistence.EntityManager;
+
+import org.apache.ambari.server.actionmanager.ActionManager;
+import org.apache.ambari.server.controller.AmbariManagementController;
+import org.apache.ambari.server.controller.internal.ClusterResourceProvider;
+import org.apache.ambari.server.events.AlertEvent;
+import org.apache.ambari.server.events.AlertReceivedEvent;
+import org.apache.ambari.server.events.MockEventListener;
+import org.apache.ambari.server.events.publishers.AlertEventPublisher;
+import org.apache.ambari.server.orm.DBAccessor;
+import org.apache.ambari.server.orm.dao.AlertDefinitionDAO;
+import org.apache.ambari.server.orm.dao.AlertsDAO;
+import org.apache.ambari.server.orm.dao.HostRoleCommandDAO;
+import org.apache.ambari.server.orm.entities.AlertCurrentEntity;
+import org.apache.ambari.server.orm.entities.AlertDefinitionEntity;
+import org.apache.ambari.server.state.Alert;
+import org.apache.ambari.server.state.AlertState;
+import org.apache.ambari.server.state.Cluster;
+import org.apache.ambari.server.state.Clusters;
+import org.apache.ambari.server.state.alert.AlertDefinition;
+import org.apache.ambari.server.state.alert.AlertDefinitionFactory;
+import org.apache.ambari.server.state.alert.ServerSource;
+import org.apache.ambari.server.state.stack.OsFamily;
+import org.easymock.EasyMock;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.common.eventbus.EventBus;
+import com.google.inject.Binder;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Module;
+
+/**
+ * Tests {@link AmbariPerformanceRunnable}.
+ */
+public class AmbariPerformanceRunnableTest {
+
+  private final static long CLUSTER_ID = 1;
+  private final static String CLUSTER_NAME = "c1";
+
+  private final static String DEFINITION_NAME = "ambari_server_performance";
+  private final static String DEFINITION_SERVICE = "AMBARI";
+  private final static String DEFINITION_COMPONENT = "AMBARI_SERVER";
+  private final static String DEFINITION_LABEL = "Mock Definition";
+  private final static int DEFINITION_INTERVAL = 1;
+
+  private Clusters m_clusters;
+  private Cluster m_cluster;
+  private Injector m_injector;
+  private AlertsDAO m_alertsDao;
+  private AlertDefinitionDAO m_definitionDao;
+  private AlertDefinitionEntity m_definition;
+  private List<AlertCurrentEntity> m_currentAlerts = new ArrayList<AlertCurrentEntity>();
+  private MockEventListener m_listener;
+
+  private AlertEventPublisher m_eventPublisher;
+  private EventBus m_synchronizedBus;
+
+  /**
+   *
+   */
+  @Before
+  public void setup() throws Exception {
+    m_injector = Guice.createInjector(new MockModule());
+    m_alertsDao = m_injector.getInstance(AlertsDAO.class);
+    m_definitionDao = m_injector.getInstance(AlertDefinitionDAO.class);
+    m_clusters = m_injector.getInstance(Clusters.class);
+    m_cluster = m_injector.getInstance(Cluster.class);
+    m_eventPublisher = m_injector.getInstance(AlertEventPublisher.class);
+    m_listener = m_injector.getInstance(MockEventListener.class);
+    m_definition = EasyMock.createNiceMock(AlertDefinitionEntity.class);
+
+    // !!! need a synchronous op for testing
+    m_synchronizedBus = new EventBus();
+    Field field = AlertEventPublisher.class.getDeclaredField("m_eventBus");
+    field.setAccessible(true);
+    field.set(m_eventPublisher, m_synchronizedBus);
+
+    // register mock listener
+    m_synchronizedBus.register(m_listener);
+
+    // create the cluster map
+    Map<String,Cluster> clusterMap = new HashMap<String, Cluster>();
+    clusterMap.put(CLUSTER_NAME, m_cluster);
+
+    // mock the definition for the alert
+    expect(m_definition.getDefinitionName()).andReturn(DEFINITION_NAME).atLeastOnce();
+    expect(m_definition.getServiceName()).andReturn(DEFINITION_SERVICE).atLeastOnce();
+    expect(m_definition.getComponentName()).andReturn(DEFINITION_COMPONENT).atLeastOnce();
+    expect(m_definition.getLabel()).andReturn(DEFINITION_LABEL).atLeastOnce();
+    expect(m_definition.getEnabled()).andReturn(true).atLeastOnce();
+    expect(m_definition.getScheduleInterval()).andReturn(DEFINITION_INTERVAL).atLeastOnce();
+
+    // mock the cluster
+    expect(m_cluster.getClusterId()).andReturn(CLUSTER_ID).atLeastOnce();
+
+    // mock clusters
+    expect(m_clusters.getClusters()).andReturn(clusterMap).atLeastOnce();
+
+    // mock the definition DAO
+    expect(m_definitionDao.findByName(CLUSTER_ID, DEFINITION_NAME)).andReturn(
+        m_definition).atLeastOnce();
+
+    // mock the current dao
+    expect(m_alertsDao.findCurrentByCluster(CLUSTER_ID)).andReturn(
+        m_currentAlerts).atLeastOnce();
+
+    // mock the factory and what it returns
+    AlertDefinition definition = new AlertDefinition();
+    definition.setDefinitionId(1L);
+    definition.setName(DEFINITION_NAME);
+    ServerSource source = new ServerSource();
+    definition.setSource(source);
+    AlertDefinitionFactory factory = m_injector.getInstance(AlertDefinitionFactory.class);
+    expect(factory.coerce(EasyMock.anyObject(AlertDefinitionEntity.class))).andReturn(definition).atLeastOnce();
+
+    replay(m_definition, m_cluster, m_clusters, m_definitionDao, m_alertsDao, factory);
+    }
+
+  /**
+   * @throws Exception
+   */
+  @After
+  public void teardown() throws Exception {
+  }
+
+  /**
+   * Tests that the event is triggerd with a status of UNKNOWN.
+   */
+  @Test
+  public void testAlertFiresEvents() {
+    // instantiate and inject mocks
+    AmbariPerformanceRunnable runnable = new AmbariPerformanceRunnable(
+        m_definition.getDefinitionName());
+
+    m_injector.injectMembers(runnable);
+
+    // run the alert
+    runnable.run();
+
+    assertEquals(1,
+        m_listener.getAlertEventReceivedCount(AlertReceivedEvent.class));
+
+    List<AlertEvent> events = m_listener.getAlertEventInstances(AlertReceivedEvent.class);
+    assertEquals(1, events.size());
+
+    AlertReceivedEvent event = (AlertReceivedEvent) events.get(0);
+    Alert alert = event.getAlert();
+    assertEquals("AMBARI", alert.getService());
+    assertEquals("AMBARI_SERVER", alert.getComponent());
+    assertEquals(AlertState.UNKNOWN, alert.getState());
+    assertEquals(DEFINITION_NAME, alert.getName());
+
+    verify(m_cluster, m_clusters, m_definitionDao);
+  }
+
+  /**
+   * Tests that the event is triggerd with a status of UNKNOWN.
+   */
+  @Test
+  public void testThresholdCalculations() {
+    // instantiate and inject mocks
+    AmbariPerformanceRunnable runnable = new AmbariPerformanceRunnable(
+        m_definition.getDefinitionName());
+
+    assertEquals(1, runnable.getThresholdValue(1, 2));
+    assertEquals(1, runnable.getThresholdValue("1", 2));
+    assertEquals(1, runnable.getThresholdValue("1.00", 2));
+    assertEquals(1, runnable.getThresholdValue("foo", 1));
+    assertEquals(1, runnable.getThresholdValue(new Object(), 1));
+  }
+
+  /**
+   *
+   */
+  private class MockModule implements Module {
+    /**
+     *
+     */
+    @Override
+    public void configure(Binder binder) {
+      Cluster cluster = EasyMock.createNiceMock(Cluster.class);
+
+      binder.bind(Clusters.class).toInstance(createNiceMock(Clusters.class));
+      binder.bind(OsFamily.class).toInstance(createNiceMock(OsFamily.class));
+      binder.bind(DBAccessor.class).toInstance(createNiceMock(DBAccessor.class));
+      binder.bind(Cluster.class).toInstance(cluster);
+      binder.bind(AlertDefinitionDAO.class).toInstance(createNiceMock(AlertDefinitionDAO.class));
+      binder.bind(AlertsDAO.class).toInstance(createNiceMock(AlertsDAO.class));
+      binder.bind(EntityManager.class).toInstance(createNiceMock(EntityManager.class));
+      binder.bind(ActionManager.class).toInstance(createNiceMock(ActionManager.class));
+      binder.bind(HostRoleCommandDAO.class).toInstance(createNiceMock(HostRoleCommandDAO.class));
+      binder.bind(AmbariManagementController.class).toInstance(createNiceMock(AmbariManagementController.class));
+      binder.bind(AlertDefinitionFactory.class).toInstance(createNiceMock(AlertDefinitionFactory.class));
+      binder.bind(ClusterResourceProvider.class).toInstance(createNiceMock(ClusterResourceProvider.class));
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/28f1a958/ambari-server/src/test/java/org/apache/ambari/server/alerts/StaleAlertRunnableTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/alerts/StaleAlertRunnableTest.java b/ambari-server/src/test/java/org/apache/ambari/server/alerts/StaleAlertRunnableTest.java
index 29b4ad5..9a9d989 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/alerts/StaleAlertRunnableTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/alerts/StaleAlertRunnableTest.java
@@ -151,19 +151,19 @@ public class StaleAlertRunnableTest {
   @Test
   public void testAllAlertsAreCurrent() {
     // create current alerts that are not stale
-    AlertDefinitionEntity definition1 = new AlertDefinitionEntity();
-    definition1.setClusterId(CLUSTER_ID);
-    definition1.setDefinitionName("foo-definition");
-    definition1.setServiceName("HDFS");
-    definition1.setComponentName("NAMENODE");
-    definition1.setEnabled(true);
-    definition1.setScheduleInterval(1);
+    AlertDefinitionEntity definition = new AlertDefinitionEntity();
+    definition.setClusterId(CLUSTER_ID);
+    definition.setDefinitionName("foo-definition");
+    definition.setServiceName("HDFS");
+    definition.setComponentName("NAMENODE");
+    definition.setEnabled(true);
+    definition.setScheduleInterval(1);
 
     AlertCurrentEntity current1 = createNiceMock(AlertCurrentEntity.class);
     AlertHistoryEntity history1 = createNiceMock(AlertHistoryEntity.class);
 
     expect(current1.getAlertHistory()).andReturn(history1).atLeastOnce();
-    expect(history1.getAlertDefinition()).andReturn(definition1).atLeastOnce();
+    expect(history1.getAlertDefinition()).andReturn(definition).atLeastOnce();
 
     expect(current1.getMaintenanceState()).andReturn(MaintenanceState.OFF).atLeastOnce();
     expect(current1.getLatestTimestamp()).andReturn(System.currentTimeMillis()).atLeastOnce();
@@ -177,7 +177,7 @@ public class StaleAlertRunnableTest {
         m_listener.getAlertEventReceivedCount(AlertReceivedEvent.class));
 
     // instantiate and inject mocks
-    StaleAlertRunnable runnable = new StaleAlertRunnable();
+    StaleAlertRunnable runnable = new StaleAlertRunnable(m_definition.getDefinitionName());
     m_injector.injectMembers(runnable);
 
     // run the alert
@@ -205,20 +205,20 @@ public class StaleAlertRunnableTest {
   @Test
   public void testStaleAlert() {
     // create current alerts that are not stale
-    AlertDefinitionEntity definition1 = new AlertDefinitionEntity();
-    definition1.setClusterId(CLUSTER_ID);
-    definition1.setDefinitionName("foo-definition");
-    definition1.setServiceName("HDFS");
-    definition1.setComponentName("NAMENODE");
-    definition1.setEnabled(true);
-    definition1.setScheduleInterval(1);
+    AlertDefinitionEntity definition = new AlertDefinitionEntity();
+    definition.setClusterId(CLUSTER_ID);
+    definition.setDefinitionName("foo-definition");
+    definition.setServiceName("HDFS");
+    definition.setComponentName("NAMENODE");
+    definition.setEnabled(true);
+    definition.setScheduleInterval(1);
 
     // create current alerts that are stale
     AlertCurrentEntity current1 = createNiceMock(AlertCurrentEntity.class);
     AlertHistoryEntity history1 = createNiceMock(AlertHistoryEntity.class);
 
     expect(current1.getAlertHistory()).andReturn(history1).atLeastOnce();
-    expect(history1.getAlertDefinition()).andReturn(definition1).atLeastOnce();
+    expect(history1.getAlertDefinition()).andReturn(definition).atLeastOnce();
 
     // a really old timestampt to trigger the alert
     expect(current1.getMaintenanceState()).andReturn(MaintenanceState.OFF).atLeastOnce();
@@ -233,7 +233,7 @@ public class StaleAlertRunnableTest {
         m_listener.getAlertEventReceivedCount(AlertReceivedEvent.class));
 
     // instantiate and inject mocks
-    StaleAlertRunnable runnable = new StaleAlertRunnable();
+    StaleAlertRunnable runnable = new StaleAlertRunnable(m_definition.getDefinitionName());
     m_injector.injectMembers(runnable);
 
     // run the alert
@@ -261,13 +261,13 @@ public class StaleAlertRunnableTest {
   @Test
   public void testStaleAlertInMaintenaceMode() {
     // create current alerts that are not stale
-    AlertDefinitionEntity definition1 = new AlertDefinitionEntity();
-    definition1.setClusterId(CLUSTER_ID);
-    definition1.setDefinitionName("foo-definition");
-    definition1.setServiceName("HDFS");
-    definition1.setComponentName("NAMENODE");
-    definition1.setEnabled(true);
-    definition1.setScheduleInterval(1);
+    AlertDefinitionEntity definition = new AlertDefinitionEntity();
+    definition.setClusterId(CLUSTER_ID);
+    definition.setDefinitionName("foo-definition");
+    definition.setServiceName("HDFS");
+    definition.setComponentName("NAMENODE");
+    definition.setEnabled(true);
+    definition.setScheduleInterval(1);
 
     // create current alerts where 1 is stale but in maintence mode
     AlertCurrentEntity current1 = createNiceMock(AlertCurrentEntity.class);
@@ -276,10 +276,10 @@ public class StaleAlertRunnableTest {
     AlertHistoryEntity history2 = createNiceMock(AlertHistoryEntity.class);
 
     expect(current1.getAlertHistory()).andReturn(history1).atLeastOnce();
-    expect(history1.getAlertDefinition()).andReturn(definition1).atLeastOnce();
+    expect(history1.getAlertDefinition()).andReturn(definition).atLeastOnce();
 
     expect(current2.getAlertHistory()).andReturn(history2).atLeastOnce();
-    expect(history2.getAlertDefinition()).andReturn(definition1).atLeastOnce();
+    expect(history2.getAlertDefinition()).andReturn(definition).atLeastOnce();
 
     // maintenance mode with a really old timestamp
     expect(current1.getMaintenanceState()).andReturn(MaintenanceState.ON).atLeastOnce();
@@ -299,7 +299,7 @@ public class StaleAlertRunnableTest {
         m_listener.getAlertEventReceivedCount(AlertReceivedEvent.class));
 
     // instantiate and inject mocks
-    StaleAlertRunnable runnable = new StaleAlertRunnable();
+    StaleAlertRunnable runnable = new StaleAlertRunnable(m_definition.getDefinitionName());
     m_injector.injectMembers(runnable);
 
     // run the alert