QueriesStatusHealthCheck.java 9.13 KB
/*
 * Decompiled with CFR 0_118.
 * 
 * Could not load the following classes:
 *  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.ReferencePolicy
 *  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
 */
package com.adobe.granite.queries.impl.hc;

import java.io.IOException;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.OperationsException;
import javax.management.QueryExp;
import javax.management.openmbean.CompositeDataSupport;
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.ReferencePolicy;
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;

@Component(metatype=1, label="Adobe Granite Queries Status Health Check", description="This heath check monitors the queries' response time.")
@Properties(value={@Property(name="hc.name", value={"Query 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={"queriesStatus"}, propertyPrivate=1)})
@Service(value={HealthCheck.class})
public class QueriesStatusHealthCheck
implements HealthCheck {
    @Property(label="Query Average Time Warning Threshold (millis)", description="The query time limit for a warning status", intValue={10})
    public static final String WARN_THRESHHOLD = "queries.status.warn_threshold";
    @Property(label="Query Critical Time Warning Threshold (millis)", description="The query time limit for an error status", intValue={15})
    public static final String ERROR_THRESHHOLD = "queries.status.error_threshold";
    private static long queryCriticalAverage;
    private static long queryWarningThreshold;
    private boolean oakInstance = false;
    @Reference(policy=ReferencePolicy.STATIC)
    MBeanServer server;

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

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

    private void setProperties(Map<String, Object> properties) {
        queryWarningThreshold = PropertiesUtil.toLong((Object)properties.get("queries.status.warn_threshold"), (long)10);
        queryCriticalAverage = PropertiesUtil.toLong((Object)properties.get("queries.status.error_threshold"), (long)15);
    }

    public Result execute() {
        FormattingResultLog resultLog = new FormattingResultLog();
        if (this.server == null) {
            resultLog.warn("Supporting components for queries health check not found.", new Object[0]);
            return new Result((ResultLog)resultLog);
        }
        try {
            ObjectName timeSeriesMbean = this.getQueryStatMBean(this.server);
            if (timeSeriesMbean != null) {
                long[] averages = this.getAverageQueryTimes(timeSeriesMbean);
                if (averages == null || averages.length == 0) {
                    resultLog.warn("No query statistics found.", new Object[0]);
                    return new Result((ResultLog)resultLog);
                }
                boolean critical = false;
                boolean warn = false;
                long maxAvg = 0;
                for (long minuteAvg : averages) {
                    if (minuteAvg > queryCriticalAverage) {
                        critical = true;
                    } else if (minuteAvg > queryWarningThreshold) {
                        warn = true;
                    }
                    if (minuteAvg <= maxAvg) continue;
                    maxAvg = minuteAvg;
                }
                resultLog.debug("[The query duration thresholds (warning, critical) can be modified via the health check configuration.]( )", new Object[0]);
                resultLog.debug("[The statistics presented here are collected over the last 60 minutes, with a 1 minute sampling interval.]( )", new Object[0]);
                long maxCount = this.getMaximumValue(timeSeriesMbean, "QueryCount");
                resultLog.info("Maximum number of queries per minute: {}.", new Object[]{maxCount});
                long maxDuration = this.getMaximumValue(timeSeriesMbean, "QueryDuration");
                resultLog.info("Maximum total query duration: {} ms.", new Object[]{maxDuration});
                resultLog.info("Maximum average query duration: {} ms.", new Object[]{maxAvg});
                if (critical) {
                    resultLog.critical("Average query duration exceeded the {} ms critical threshold.", new Object[]{queryCriticalAverage});
                    resultLog.debug("[Check the Query Performance tool in the Diagnosis page for more details about the longest running queries.](/libs/granite/operations/content/diagnosis/tool.html/granite:queryperformance)", new Object[0]);
                } else if (warn) {
                    resultLog.warn("Average query duration exceeded the {} ms warning threshold.", new Object[]{queryWarningThreshold});
                    resultLog.debug("[Check the Query Performance tool in the Diagnosis page for more details about the longest running queries](/libs/granite/operations/content/diagnosis/tool.html/granite:queryperformance)", new Object[0]);
                } else {
                    resultLog.info("Average query duration is below the {} ms warning threshold.", new Object[]{queryWarningThreshold});
                }
            } else {
                resultLog.warn("No query statistics found.", new Object[0]);
            }
        }
        catch (Exception e) {
            resultLog.warn(e.getLocalizedMessage(), new Object[0]);
        }
        return new Result((ResultLog)resultLog);
    }

    private ObjectName getQueryStatMBean(MBeanServer server) throws OperationsException, IOException {
        Set<ObjectName> names = server.queryNames(new ObjectName("com.adobe.granite:type=TimeSeries,name=QUERY_AVERAGE,*"), null);
        if (names.isEmpty()) {
            names = this.queryNames(server, "RepositoryStats");
            if (names.isEmpty()) {
                names = this.queryNames(server, "\"RepositoryStats\"");
                if (names.isEmpty()) {
                    return null;
                }
                this.oakInstance = true;
            } else {
                this.oakInstance = true;
            }
        }
        return names.iterator().next();
    }

    private Set<ObjectName> queryNames(MBeanServer server, String name) throws OperationsException, IOException {
        return server.queryNames(new ObjectName("org.apache.jackrabbit.oak:type=" + name + ",*"), null);
    }

    private long[] getAverageQueryTimes(ObjectName mbean) throws Exception {
        if (this.oakInstance) {
            Object queryAvgObj = this.server.getAttribute(mbean, "QueryAverage");
            Object avgMinObj = ((CompositeDataSupport)queryAvgObj).get("per minute");
            if (!(avgMinObj instanceof long[])) {
                return null;
            }
            return (long[])avgMinObj;
        }
        Object avgObj = this.server.getAttribute(mbean, "ValuePerMinute");
        if (!(avgObj instanceof long[])) {
            return null;
        }
        return (long[])this.server.getAttribute(mbean, "ValuePerMinute");
    }

    private long getMaximumValue(ObjectName mbean, String propertyName) throws Exception {
        Object queryStatsPerMinute;
        long max = 0;
        Object queryObj = this.server.getAttribute(mbean, propertyName);
        if (queryObj != null && (queryStatsPerMinute = ((CompositeDataSupport)queryObj).get("per minute")) != null && queryStatsPerMinute instanceof long[]) {
            long[] counts = (long[])queryStatsPerMinute;
            for (int i = 0; i < counts.length; ++i) {
                if (max >= counts[i]) continue;
                max = counts[i];
            }
        }
        return max;
    }

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

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