package org.stagemonitor.alerting.incident; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonCreator; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import org.stagemonitor.alerting.check.Check; import org.stagemonitor.alerting.check.CheckResult; import org.stagemonitor.core.MeasurementSession; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.ANY; import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.NONE; @JsonAutoDetect(fieldVisibility = ANY, getterVisibility = ANY, setterVisibility = NONE) public class Incident { private int version = 1; private Date firstFailureAt; private Date resolvedAt; private CheckResult.Status oldStatus; private CheckResult.Status newStatus; private String checkId; private String checkName; private int consecutiveFailures; @JsonIgnore private Map<String, CheckResults> checkResultsByMeasurementSessionId = new HashMap<String, CheckResults>(); public static CheckResult.Status getMostSevereStatus(Collection<Incident> incidents) { CheckResult.Status mostSevereStatus = CheckResult.Status.OK; for (Incident incident : incidents) { if (incident.getNewStatus().isMoreSevere(mostSevereStatus)) { mostSevereStatus = incident.getNewStatus(); } } return mostSevereStatus; } public Incident() { } @JsonCreator public Incident(@JsonProperty("checkResults") Collection<CheckResults> checkResults) { for (CheckResults checkResult : checkResults) { checkResultsByMeasurementSessionId.put(checkResult.getMeasurementSession().getId(), checkResult); } } public Incident(Check check, MeasurementSession measurementSession, List<CheckResult> checkResults) { this.newStatus = CheckResult.getMostSevereStatus(checkResults); this.checkId = check.getId(); this.checkName = check.getName(); this.firstFailureAt = new Date(); setCheckResults(measurementSession, checkResults, 0); } public Incident(Incident previousIncident, MeasurementSession measurementSession, List<CheckResult> checkResults) { version = previousIncident.version + 1; oldStatus = previousIncident.newStatus; checkId = previousIncident.checkId; checkName = previousIncident.checkName; checkResultsByMeasurementSessionId = previousIncident.checkResultsByMeasurementSessionId; firstFailureAt = previousIncident.getFirstFailureAt(); setCheckResults(measurementSession, checkResults, previousIncident.consecutiveFailures); } private void setCheckResults(MeasurementSession measurementSession, List<CheckResult> checkResults, int previousConsecutiveFailures) { if (checkResults.isEmpty()) { checkResultsByMeasurementSessionId.remove(measurementSession.getId()); } else if (checkResultsByMeasurementSessionId.containsKey(measurementSession.getId())) { checkResultsByMeasurementSessionId.put(measurementSession.getId(), new CheckResults(checkResultsByMeasurementSessionId.get(measurementSession.getId()), checkResults)); } else { checkResultsByMeasurementSessionId.put(measurementSession.getId(), new CheckResults(measurementSession, checkResults)); } newStatus = getMostSevereStatus(); if (newStatus == CheckResult.Status.OK) { resolvedAt = new Date(); } consecutiveFailures = Math.max(previousConsecutiveFailures, getConsecutiveFailuresFromCheckResults()); } public Collection<CheckResults> getCheckResults() { return checkResultsByMeasurementSessionId.values(); } public Collection<String> getHosts() { Set<String> hosts = new TreeSet<String>(); for (CheckResults checkResult : checkResultsByMeasurementSessionId.values()) { hosts.add(checkResult.getMeasurementSession().getHostName()); } return hosts; } public Collection<String> getInstances() { Set<String> instances = new TreeSet<String>(); for (CheckResults checkResult : checkResultsByMeasurementSessionId.values()) { instances.add(checkResult.getMeasurementSession().getInstanceName()); } return instances; } private CheckResult.Status getMostSevereStatus() { LinkedList<CheckResult> results = new LinkedList<CheckResult>(); for (CheckResults checkResults : checkResultsByMeasurementSessionId.values()) { results.addAll(checkResults.getResults()); } return CheckResult.getMostSevereStatus(results); } @JsonIgnore private int getConsecutiveFailuresFromCheckResults() { int consecutiveFailures = 0; for (CheckResults checkResultByInstance : checkResultsByMeasurementSessionId.values()) { consecutiveFailures = Math.max(consecutiveFailures, checkResultByInstance.getConsecutiveFailures()); } return consecutiveFailures; } public int getConsecutiveFailures() { return consecutiveFailures; } /** * The version is used for optimistic concurrency control * * @return the version */ public int getVersion() { return version; } public void setVersion(int version) { this.version = version; } public Date getFirstFailureAt() { return firstFailureAt; } public void setFirstFailureAt(Date firstFailureAt) { this.firstFailureAt = firstFailureAt; } public CheckResult.Status getOldStatus() { return oldStatus; } public void setOldStatus(CheckResult.Status oldStatus) { this.oldStatus = oldStatus; } public CheckResult.Status getNewStatus() { return newStatus; } public void setNewStatus(CheckResult.Status newStatus) { this.newStatus = newStatus; } /** * The id of the corresponding {@link org.stagemonitor.alerting.check.Check}. * It is also used as the primary key for the incident. * * @return the id of the corresponding {@link org.stagemonitor.alerting.check.Check}. */ public String getCheckId() { return checkId; } public void setCheckId(String checkId) { this.checkId = checkId; } public String getCheckName() { return checkName; } public void setCheckName(String checkName) { this.checkName = checkName; } public boolean hasStageChange() { return oldStatus != newStatus; } public Date getResolvedAt() { return resolvedAt; } public void setResolvedAt(Date resolvedAt) { this.resolvedAt = resolvedAt; } public int getFailedChecks() { int failedChecks = 0; for (CheckResults checkResults : getCheckResults()) { failedChecks += checkResults.getResults().size(); } return failedChecks; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Incident incident = (Incident) o; if (version != incident.version) return false; return !(checkId != null ? !checkId.equals(incident.checkId) : incident.checkId != null); } @Override public int hashCode() { int result = version; result = 31 * result + (checkId != null ? checkId.hashCode() : 0); return result; } @JsonIgnore public Incident getIncidentWithPreviousVersion() { final Incident incident = new Incident(); incident.setCheckId(checkId); incident.setVersion(version - 1); return incident; } public boolean isAlertIncident(Check check) { if (isBackToOk() && hasEnoughConsecutiveFailures(check)) { return true; } if (hasStageChange() && hasEnoughConsecutiveFailures(check)) { return true; } return getConsecutiveFailures() == check.getAlertAfterXFailures(); } private boolean hasEnoughConsecutiveFailures(Check check) { return getConsecutiveFailures() >= check.getAlertAfterXFailures(); } private boolean isBackToOk() { return hasStageChange() && newStatus == CheckResult.Status.OK; } @Override public String toString() { StringBuilder sb = new StringBuilder() .append("Incident for check group '").append(checkName).append("':\n") .append("firstFailureAt=").append(firstFailureAt).append("\n"); if (resolvedAt == null) { sb.append("resolvedAt=").append(resolvedAt).append("\n"); } sb.append("oldStatus=").append(oldStatus).append("\n") .append("newStatus=").append(newStatus).append("\n"); if (!checkResultsByMeasurementSessionId.isEmpty()) { sb.append("host|instance|status|description|current value\n") .append("----|--------|------|-----------|-------------\n"); for (CheckResults checkResult : checkResultsByMeasurementSessionId.values()) { MeasurementSession measurement = checkResult.getMeasurementSession(); for (CheckResult result : checkResult.getResults()) { sb.append(measurement.getHostName()).append('|') .append(measurement.getInstanceName()).append('|') .append(result.getStatus()).append('|') .append(result.getFailingExpression()).append('|') .append(result.getCurrentValue()).append("\n"); } } } return sb.toString(); } }