/* * 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 + "]"); } } }