package org.stagemonitor.alerting.annotation; import com.codahale.metrics.annotation.ExceptionMetered; import com.codahale.metrics.annotation.Timed; import net.bytebuddy.description.annotation.AnnotationList; import net.bytebuddy.description.method.MethodDescription; import net.bytebuddy.matcher.ElementMatcher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.stagemonitor.alerting.AlertingPlugin; import org.stagemonitor.alerting.check.Check; import org.stagemonitor.alerting.check.MetricValueType; import org.stagemonitor.alerting.check.Threshold; import org.stagemonitor.core.MeasurementSession; import org.stagemonitor.core.instrument.AbstractClassPathScanner; import org.stagemonitor.core.metrics.annotations.ExceptionMeteredTransformer; import org.stagemonitor.core.metrics.annotations.TimedTransformer; import org.stagemonitor.core.metrics.metrics2.MetricName; import org.stagemonitor.tracing.AbstractMonitorRequestsTransformer; import org.stagemonitor.tracing.MonitorRequests; import org.stagemonitor.tracing.metrics.ServerRequestMetricsSpanEventListener; import java.io.IOException; import java.util.LinkedList; import java.util.List; import static net.bytebuddy.matcher.ElementMatchers.isAnnotatedWith; public class SlaCheckCreatingClassPathScanner extends AbstractClassPathScanner { private static final Logger logger = LoggerFactory.getLogger(SlaCheckCreatingClassPathScanner.class); private static final Object measurementSessionLock = new Object(); private static List<Check> checksCreatedBeforeMeasurementStarted = new LinkedList<Check>(); private static volatile MeasurementSession measurementSession; @Override protected ElementMatcher.Junction<MethodDescription> getExtraMethodElementMatcher() { return isAnnotatedWith(SLA.class).or(isAnnotatedWith(SLAs.class)); } @Override protected void onMethodMatch(MethodDescription.InDefinedShape methodDescription) { final String fullMethodSignature = methodDescription.toString(); final AnnotationList methodAnnotations = methodDescription.getDeclaredAnnotations(); TimerNames timerNames = getTimerNames(methodDescription, methodAnnotations); if (timerNames.hasAnyName()) { createChecks(fullMethodSignature, methodAnnotations, timerNames); } else { timerNames = getTimerNames(methodDescription, methodDescription.getDeclaringType().getInheritedAnnotations()); createChecks(fullMethodSignature, methodAnnotations, timerNames); } } private static class TimerNames { private MetricName timerMetricName; private String timerName; private String errorRequestName; private MetricName errorMetricName; boolean hasAnyName() { return timerName != null || errorRequestName != null; } } private TimerNames getTimerNames(MethodDescription.InDefinedShape methodDescription, AnnotationList declaredAnnotations) { TimerNames timerNames = new TimerNames(); if (declaredAnnotations.isAnnotationPresent(MonitorRequests.class)) { timerNames.timerName = AbstractMonitorRequestsTransformer.getRequestName(methodDescription); if (timerNames.timerName != null) { timerNames.timerMetricName = ServerRequestMetricsSpanEventListener.getTimerMetricName(timerNames.timerName); timerNames.errorRequestName = timerNames.timerName; timerNames.errorMetricName = ServerRequestMetricsSpanEventListener.getErrorMetricName(timerNames.timerName); } } else { if (declaredAnnotations.isAnnotationPresent(Timed.class)) { timerNames.timerName = new TimedTransformer.TimedSignatureDynamicValue().getRequestName(methodDescription); timerNames.timerMetricName = TimedTransformer.getTimerName(timerNames.timerName); } if (declaredAnnotations.isAnnotationPresent(ExceptionMetered.class)) { timerNames.errorRequestName = new ExceptionMeteredTransformer.ExceptionMeteredSignatureDynamicValue().getRequestName(methodDescription); timerNames.errorMetricName = ExceptionMeteredTransformer.getMetricName(timerNames.errorRequestName); } } return timerNames; } private void createChecks(String fullMethodSignature, AnnotationList declaredAnnotations, TimerNames timerNames) { if (declaredAnnotations.isAnnotationPresent(SLA.class)) { createSlaCheck(declaredAnnotations.ofType(SLA.class).loadSilent(), fullMethodSignature, timerNames); } if (declaredAnnotations.isAnnotationPresent(SLAs.class)) { for (SLA sla : declaredAnnotations.ofType(SLAs.class).loadSilent().value()) { createSlaCheck(sla, fullMethodSignature, timerNames); } } } private static void createSlaCheck(SLA slaAnnotation, String fullMethodSignature, TimerNames timerNames) { if (slaAnnotation.metric().length > 0) { addResponseTimeCheck(slaAnnotation, fullMethodSignature, timerNames); } if (slaAnnotation.errorRateThreshold() >= 0) { addErrorRateCheck(slaAnnotation, fullMethodSignature, timerNames); } } private static void addResponseTimeCheck(SLA slaAnnotation, String fullMethodSignature, TimerNames timerNames) { MetricValueType[] valueTypes = slaAnnotation.metric(); double[] thresholdValues = slaAnnotation.threshold(); if (valueTypes.length != thresholdValues.length) { logger.warn("The number of provided metrics don't match the number of provided thresholds in @SLA {}", fullMethodSignature); return; } if (timerNames.timerName == null) { logger.warn("To create a timer SLA for the method {}, it also has to be annotated with @MonitorRequests or " + " @Timed. When using @MonitorRequests, resolveNameAtRuntime must not be set to true.", fullMethodSignature); return; } Check check = createCheck(slaAnnotation, fullMethodSignature, timerNames.timerName, timerNames.timerMetricName, " (response time)", "responseTime"); final List<Threshold> thresholds = check.getThresholds(slaAnnotation.severity()); for (int i = 0; i < valueTypes.length; i++) { thresholds.add(new Threshold(valueTypes[i].getName(), slaAnnotation.operator(), thresholdValues[i])); } addCheckIfStarted(check); } private static void addErrorRateCheck(SLA slaAnnotation, String fullMethodSignature, TimerNames timerNames) { if (timerNames.errorRequestName == null) { logger.warn("To create an error SLA for the method {}, it also has to be annotated with @MonitorRequests or " + " @ExceptionMetered. When using @MonitorRequests, resolveNameAtRuntime must not be set to true.", fullMethodSignature); return; } final Check check = createCheck(slaAnnotation, fullMethodSignature, timerNames.errorRequestName, timerNames.errorMetricName, " (errors)", "errors"); final Threshold t = new Threshold(MetricValueType.M1_RATE.getName(), Threshold.Operator.LESS, slaAnnotation.errorRateThreshold()); check.getThresholds(slaAnnotation.severity()).add(t); addCheckIfStarted(check); } private static Check createCheck(SLA slaAnnotation, String fullMethodSignature, String requestName, MetricName metricName, String checkNameSuffix, String checkIdSuffix) { Check check = new Check(); check.setId(fullMethodSignature + "." + checkIdSuffix); check.setName(requestName + checkNameSuffix); check.setTarget(metricName); check.setAlertAfterXFailures(slaAnnotation.alertAfterXFailures()); return check; } private static void addCheckIfStarted(Check check) { synchronized (measurementSessionLock) { if (measurementSession != null) { addCheck(check, measurementSession); } else { checksCreatedBeforeMeasurementStarted.add(check); } } } private static void addCheck(Check check, MeasurementSession measurementSession) { check.setApplication(measurementSession.getApplicationName()); try { configuration.getConfig(AlertingPlugin.class).addCheck(check); } catch (IOException e) { logger.warn(e.getMessage(), e); } } public static void onStart(MeasurementSession measurementSession) { synchronized (measurementSessionLock) { SlaCheckCreatingClassPathScanner.measurementSession = measurementSession; for (Check check : checksCreatedBeforeMeasurementStarted) { addCheck(check, measurementSession); } checksCreatedBeforeMeasurementStarted.clear(); } } }