/*
* RHQ Management Platform
* Copyright (C) 2005-2008 Red Hat, Inc.
* All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation version 2 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
package org.rhq.enterprise.server.alert;
import java.util.EnumSet;
import java.util.List;
import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.rhq.core.domain.alert.Alert;
import org.rhq.core.domain.alert.AlertDampening;
import org.rhq.core.domain.alert.AlertDampeningEvent;
import org.rhq.core.domain.alert.AlertDampeningEvent.Type;
import org.rhq.core.domain.alert.AlertDefinition;
import org.rhq.enterprise.server.RHQConstants;
/**
* @author Joseph Marques
*/
@Stateless
public class AlertDampeningManagerBean implements AlertDampeningManagerLocal {
private final Log log = LogFactory.getLog(AlertDampeningManagerBean.class);
@EJB
private AlertManagerLocal alertManager;
@PersistenceContext(unitName = RHQConstants.PERSISTENCE_UNIT_NAME)
private EntityManager entityManager;
@Override
public AlertDampeningEvent getLatestEventByAlertDefinitionId(int alertDefinitionId) {
Query latestEventQuery = entityManager
.createNamedQuery(AlertDampeningEvent.QUERY_FIND_LATEST_BY_ALERT_DEFINITION_ID);
latestEventQuery.setParameter("alertDefinitionId", alertDefinitionId);
try {
AlertDampeningEvent latestEvent = (AlertDampeningEvent) latestEventQuery.getSingleResult();
return latestEvent;
} catch (NoResultException nre) {
return null; // expected a good deal of the time
}
}
@SuppressWarnings("unchecked")
private boolean shouldFireDurationCountAlert(int alertDefinitionId, int eventCountThreshold, long lastSeconds) {
long oldestEventTime = System.currentTimeMillis() - (lastSeconds * 1000);
Query query = entityManager.createNamedQuery(AlertDampeningEvent.QUERY_FIND_BY_TIME_AND_TYPES);
query.setParameter("alertDefinitionId", alertDefinitionId);
query.setParameter("eventTypes", EnumSet.of(Type.POSITIVE, Type.POSITIVE_AGAIN));
query.setParameter("oldestEventTime", oldestEventTime);
/*
* Make sure we get at most the number of events we need in order to trigger the DURATION_COUNT dampening rule
*/
query.setMaxResults(eventCountThreshold);
List<AlertDampeningEvent> oldestEvents = query.getResultList();
deleteAlertEventsOlderThan(alertDefinitionId, oldestEventTime);
// if we have enough, it'll be exactly equal to the number need (thanks to setMaxResults)
boolean shouldFire = (oldestEvents.size() == eventCountThreshold);
if (log.isDebugEnabled()) {
log.debug("Need " + eventCountThreshold + " events " + "for the last " + lastSeconds + " seconds" + ", "
+ "found " + oldestEvents.size());
}
if (shouldFire) {
for (AlertDampeningEvent event : oldestEvents) {
event.setAlertDefinition(null);
entityManager.remove(event);
}
}
// let the caller know what happened, so they can appropriately fire an alert or not
return shouldFire;
}
@Override
public Alert processEventType(int alertDefinitionId, AlertDampeningEvent.Type eventType) {
/*
* some dampening event occurred, handle it accordingly. if it was positive, check whether this
* AlertDefinition can fire an alert according to its dampening category rules. currently, these is no
* supported dampening event that can fire as the result of a partial condition set match.
*/
Alert firedAlert = null;
try {
boolean fire = false;
// get the alert definition in preparation for lots of processing on it
AlertDefinition alertDefinition = entityManager.find(AlertDefinition.class, alertDefinitionId);
AlertDampening alertDampening = alertDefinition.getAlertDampening();
AlertDampening.Category category = alertDampening.getCategory();
if (log.isDebugEnabled()) {
log.debug("Alert condition processing for " + alertDefinition);
log.debug("Dampening rules are: " + alertDampening);
}
if (category == AlertDampening.Category.NONE) {
if ((eventType == AlertDampeningEvent.Type.POSITIVE)
|| (eventType == AlertDampeningEvent.Type.POSITIVE_AGAIN)) {
/*
* technically we should always fire for the NONE category, but since this method has other
* consequences we'll call it and pass 1 as the second argument
*/
fire = this.shouldFireConsecutiveCountAlert(alertDefinitionId, 1);
}
} else if (category == AlertDampening.Category.CONSECUTIVE_COUNT) {
/*
* we don't care if the condition set becomes untrue, we need a number of events to be true in a row; a
* false event effectively resets that counter, so we need not perform a check in that instance
*/
if ((eventType == AlertDampeningEvent.Type.POSITIVE)
|| (eventType == AlertDampeningEvent.Type.POSITIVE_AGAIN)) {
int count = alertDampening.getValue();
fire = this.shouldFireConsecutiveCountAlert(alertDefinitionId, count);
}
} else if (category == AlertDampening.Category.PARTIAL_COUNT) {
if ((eventType == AlertDampeningEvent.Type.POSITIVE)
|| (eventType == AlertDampeningEvent.Type.POSITIVE_AGAIN)) {
int count = alertDampening.getValue();
int period = alertDampening.getPeriod();
fire = this.shouldFirePartialCountAlert(alertDefinitionId, count, period);
}
} else if (category == AlertDampening.Category.DURATION_COUNT) {
/*
* we don't care if the condition set becomes untrue, the count is all about how many times it was known
* to be positive (or positive again) during the collection period
*/
if ((eventType == AlertDampeningEvent.Type.POSITIVE)
|| (eventType == AlertDampeningEvent.Type.POSITIVE_AGAIN)) {
int count = alertDampening.getValue();
long period = alertDampening.getPeriod() * alertDampening.getPeriodUnits().getNumberOfSeconds();
// check whether "value" number of positive events have occurred within the last "period" seconds
fire = this.shouldFireDurationCountAlert(alertDefinitionId, count, period);
}
} else {
log.info("Category " + alertDampening.getCategory()
+ " is not supported for alert dampening processing");
}
/*
* If the dampening rules say we can fire, then create a new alert, and find the unmatched alert condition
* logs and attach them to this new alert.
*/
if (fire) {
log.debug("Dampening rules were satisfied");
firedAlert = alertManager.fireAlert(alertDefinitionId);
} else {
log.debug("Dampening rules were not satisfied");
}
} catch (Exception e) {
firedAlert = null;
log.error("Error operating on the passed dampening eventType of " + eventType + " "
+ "for the alert definition with id of " + alertDefinitionId, e);
}
return firedAlert;
}
private boolean shouldFireConsecutiveCountAlert(int alertDefinitionId, long count) {
// consecutive is a more specific case of general where count == period
return shouldFirePartialCountAlert(alertDefinitionId, count, count);
}
private boolean shouldFirePartialCountAlert(int alertDefinitionId, long countNeeded, long period) {
List<AlertDampeningEvent> events = getRecentAlertDampeningEvents(alertDefinitionId, period);
deleteAlertEventsOlderThan(alertDefinitionId, events.get(events.size() - 1).getEventTime());
long positiveFires = 0;
for (AlertDampeningEvent event : events) {
if ((event.getEventType() == AlertDampeningEvent.Type.POSITIVE)
|| (event.getEventType() == AlertDampeningEvent.Type.POSITIVE_AGAIN)) {
positiveFires++;
}
}
if (log.isDebugEnabled()) {
log.debug("Need " + countNeeded + " events " + "for the last " + period + " events" + ", " + "found "
+ positiveFires);
}
if (positiveFires >= countNeeded) {
for (AlertDampeningEvent event : events) {
event.setAlertDefinition(null);
entityManager.remove(event);
}
return true;
}
return false;
}
@SuppressWarnings("unchecked")
private List<AlertDampeningEvent> getRecentAlertDampeningEvents(int alertDefinitionId, long maxResults) {
Query query = entityManager.createNamedQuery(AlertDampeningEvent.QUERY_FIND_BY_ALERT_DEFINITION_ID);
query.setParameter("alertDefinitionId", alertDefinitionId);
query.setMaxResults((int) maxResults);
List<AlertDampeningEvent> results = query.getResultList();
return results;
}
private void deleteAlertEventsOlderThan(Integer alertDefinitionId, long olderThan) {
Query query = entityManager.createNamedQuery(AlertDampeningEvent.QUERY_DELETE_BY_TIMESTAMP);
query.setParameter("alertDefinitionId", alertDefinitionId);
query.setParameter("oldest", olderThan);
int deletedCount = query.executeUpdate();
if (deletedCount > 0 && log.isDebugEnabled()) {
log.debug("Deleted " + deletedCount + " stale AlertDampeningEvent" + ((deletedCount == 1) ? "" : "s")
+ " for AlertDefinition[id=" + alertDefinitionId + "]");
}
}
}