RequestsStatusHealthCheckImpl.java 10.3 KB
/*
 * Decompiled with CFR 0_118.
 * 
 * Could not load the following classes:
 *  com.codahale.metrics.JmxReporter
 *  com.codahale.metrics.JmxReporter$Builder
 *  com.codahale.metrics.MetricRegistry
 *  com.codahale.metrics.ObjectNameFactory
 *  org.apache.felix.scr.annotations.Activate
 *  org.apache.felix.scr.annotations.Component
 *  org.apache.felix.scr.annotations.Modified
 *  org.apache.felix.scr.annotations.Properties
 *  org.apache.felix.scr.annotations.Property
 *  org.apache.felix.scr.annotations.PropertyUnbounded
 *  org.apache.felix.scr.annotations.Reference
 *  org.apache.felix.scr.annotations.Service
 *  org.apache.sling.commons.osgi.PropertiesUtil
 *  org.apache.sling.hc.api.HealthCheck
 *  org.apache.sling.hc.api.Result
 *  org.apache.sling.hc.api.ResultLog
 *  org.apache.sling.hc.util.FormattingResultLog
 *  org.slf4j.Logger
 *  org.slf4j.LoggerFactory
 */
package com.adobe.granite.requests.logging.impl.hc;

import com.codahale.metrics.JmxReporter;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.ObjectNameFactory;
import java.io.IOException;
import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import javax.management.MBeanAttributeInfo;
import javax.management.MBeanInfo;
import javax.management.MBeanServer;
import javax.management.MBeanServerConnection;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.management.OperationsException;
import javax.management.QueryExp;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Modified;
import org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.PropertyUnbounded;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.apache.sling.commons.osgi.PropertiesUtil;
import org.apache.sling.hc.api.HealthCheck;
import org.apache.sling.hc.api.Result;
import org.apache.sling.hc.api.ResultLog;
import org.apache.sling.hc.util.FormattingResultLog;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Component(metatype=1, label="Adobe Granite Requests Status Health Check", description="This health check monitors the html content requests' response time.")
@Properties(value={@Property(name="hc.name", value={"Request Performance"}, propertyPrivate=1), @Property(name="hc.tags", unbounded=PropertyUnbounded.ARRAY, label="Tags", description="Tags for this health check to be used by composite health checks."), @Property(name="hc.mbean.name", value={"requestsStatus"}, propertyPrivate=1)})
@Service(value={HealthCheck.class})
public final class RequestsStatusHealthCheckImpl
implements HealthCheck {
    private static final Logger logger = LoggerFactory.getLogger(RequestsStatusHealthCheckImpl.class);
    @Reference
    private MBeanServer server;
    @Reference
    MetricRegistry registry;
    @Property(label="Statistics Time Warning Threshold (milliseconds)", description="The request time limit for a warning status", intValue={200})
    public static final String WARN_THRESHHOLD = "statistics.request.warn_threshold";
    @Property(label="Statistics Time Error Threshold (milliseconds)", description="The request time limit for an error status", intValue={500})
    public static final String ERROR_THRESHOLD = "statistics.request.error_threshold";
    @Property(label="Statistics Minimum Number of Requests", description="The minimum number of requests required to run the health check", intValue={50})
    public static final String MINIMUM_NUMBER_OF_REQUESTS = "statistics.request.min_count";
    private static final String METRICS_BEAN_TYPE = "granite.metrics";
    private static long warnThreshold;
    private static long errorThreshold;
    private static int minNumberOfRequests;
    private static String diagnosticPercentile;
    private static NumberFormat formatter;
    private HashMap<String, String> LABELS;

    public RequestsStatusHealthCheckImpl() {
        this.LABELS = this.initializeLabels();
    }

    private HashMap<String, String> initializeLabels() {
        HashMap<String, String> map = new HashMap<String, String>();
        map.put("75thPercentile", "75th Percentile (ms)");
        map.put("95thPercentile", "95th Percentile (ms)");
        map.put("98thPercentile", "98th Percentile (ms)");
        map.put("99thPercentile", "99th Percentile (ms)");
        map.put("999thPercentile", "99.9th Percentile (ms)");
        map.put("Mean", "Mean Duration (ms)");
        map.put("StdDev", "Duration Standard Deviation (ms)");
        map.put("50thPercentile", "50th Percentile (ms)");
        map.put("DurationUnit", "Duration Unit");
        map.put("Max", "Maximum Duration (ms)");
        map.put("Min", "Minimum Duration (ms)");
        map.put("FifteenMinuteRate", "Fifteen Minute Rate");
        map.put("FiveMinuteRate", "Five Minute Rate");
        map.put("MeanRate", "Mean Rate");
        map.put("OneMinuteRate", "One Minute Rate");
        map.put("Count", "Total number of requests");
        map.put("RateUnit", "Rate Unit");
        return map;
    }

    @Activate
    protected void activate(Map<String, Object> props) {
        this.setProperties(props);
    }

    @Modified
    protected void update(Map<String, Object> props) {
        this.setProperties(props);
    }

    private void setProperties(Map<String, Object> props) {
        warnThreshold = PropertiesUtil.toLong((Object)props.get("statistics.request.warn_threshold"), (long)200);
        errorThreshold = PropertiesUtil.toLong((Object)props.get("statistics.request.error_threshold"), (long)500);
        minNumberOfRequests = PropertiesUtil.toInteger((Object)props.get("statistics.request.min_count"), (int)50);
    }

    public Result execute() {
        FormattingResultLog resultLog = new FormattingResultLog();
        resultLog.debug("[This health check looks at the {} in relation to warning and critical thresholds. The thresholds can be modified via the configuration manager.](/system/console/configMgr/com.adobe.granite.requests.logging.impl.hc.RequestsStatusHealthCheckImpl)", new Object[]{this.LABELS.get(diagnosticPercentile)});
        double percentile = this.reportResults(resultLog);
        if (percentile == -1.0) {
            resultLog.debug("The number of requests performed is low, and the statistics obtained may be skewed. Please run the health check again after more data has been gathered.", new Object[0]);
        } else if (percentile > (double)errorThreshold) {
            resultLog.critical("The {} is above the critical duration threshold ({}).", new Object[]{this.LABELS.get(diagnosticPercentile), errorThreshold});
        } else if (percentile > (double)warnThreshold) {
            resultLog.warn("The {} is above the high duration threshold ({}).", new Object[]{this.LABELS.get(diagnosticPercentile), warnThreshold});
        }
        return new Result((ResultLog)resultLog);
    }

    /*
     * WARNING - Removed try catching itself - possible behaviour change.
     */
    private double reportResults(FormattingResultLog resultLog) {
        Double percentile;
        block7 : {
            percentile = 0.0;
            JmxReporter reporter = JmxReporter.forRegistry((MetricRegistry)this.registry).createsObjectNamesWith((ObjectNameFactory)new GraniteNameFactory()).registerWith(this.server).build();
            reporter.start();
            try {
                ObjectName metricsMbean = this.getStatsMBean(this.server, "granite.metrics");
                MBeanInfo info = this.server.getMBeanInfo(metricsMbean);
                MBeanAttributeInfo[] attrInfo = info.getAttributes();
                if (attrInfo == null) break block7;
                for (MBeanAttributeInfo attr : attrInfo) {
                    long requestCount;
                    String attrName = attr.getName();
                    Object value = this.server.getAttribute(metricsMbean, attr.getName());
                    String printableValue = value instanceof Double ? formatter.format(value) : value.toString();
                    resultLog.debug("{}: {}", new Object[]{this.LABELS.get(attrName), printableValue});
                    if (diagnosticPercentile.equals(attrName)) {
                        percentile = (Double)value;
                        continue;
                    }
                    if (!"Count".equals(attrName) || (requestCount = ((Long)value).longValue()) >= (long)minNumberOfRequests) continue;
                    percentile = -1.0;
                    break;
                }
            }
            catch (Exception e) {
                logger.error("Exception occurred while getting metrics: ", (Throwable)e);
            }
            finally {
                reporter.close();
            }
        }
        return percentile;
    }

    private ObjectName getStatsMBean(MBeanServerConnection server, String type) throws OperationsException, IOException {
        Set<ObjectName> names = server.queryNames(new ObjectName("metrics:type=" + type + ",*"), null);
        return names.iterator().next();
    }

    static {
        diagnosticPercentile = "75thPercentile";
        formatter = new DecimalFormat("#0.00");
    }

    protected void bindServer(MBeanServer mBeanServer) {
        this.server = mBeanServer;
    }

    protected void unbindServer(MBeanServer mBeanServer) {
        if (this.server == mBeanServer) {
            this.server = null;
        }
    }

    protected void bindRegistry(MetricRegistry metricRegistry) {
        this.registry = metricRegistry;
    }

    protected void unbindRegistry(MetricRegistry metricRegistry) {
        if (this.registry == metricRegistry) {
            this.registry = null;
        }
    }

    private class GraniteNameFactory
    implements ObjectNameFactory {
        private GraniteNameFactory() {
        }

        public ObjectName createName(String type, String domain, String name) {
            Hashtable<String, String> table = new Hashtable<String, String>();
            table.put("type", "granite.metrics");
            table.put("name", name);
            try {
                return new ObjectName(domain, table);
            }
            catch (MalformedObjectNameException e) {
                throw new RuntimeException(e);
            }
        }
    }

}