All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.zodiac.actuate.health.checker.TimerHealthIndicator Maven / Gradle / Ivy

There is a newer version: 1.6.8
Show newest version
package org.zodiac.actuate.health.checker;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Deque;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.stream.Collectors;

import org.springframework.boot.actuate.health.AbstractHealthIndicator;
import org.springframework.boot.actuate.health.Health.Builder;
import org.zodiac.commons.logging.SmartSlf4jLogger;
import org.zodiac.commons.logging.SmartSlf4jLoggerFactory;
import org.zodiac.commons.util.Colls;
import org.zodiac.commons.util.serialize.JsonUtil;
import org.zodiac.monitor.metrics.health.HealthUtil;
import org.zodiac.monitor.metrics.health.timer.TimerStat;
import org.zodiac.monitor.metrics.micrometer.advice.timer.TimerRecorder;
import org.zodiac.monitor.metrics.micrometer.config.MicrometerAdviceTimerInfo;

/**
 * Analysis and statistical call time dimension related health messages .
 * 
 * Deprecated: It should not be checked on the indicator monitoring client side,
 * but should be checked uniformly on the prometheus server side (which is also
 * convenient for unified configuration)
 * 
 */
@Deprecated
public class TimerHealthIndicator extends AbstractHealthIndicator implements TimerRecorder {

    private static final Comparator MAX_COMPARATOR = new Comparator() {
        @Override
        public int compare(TimerStat o1, TimerStat o2) {
            return (int) (o1.getMax() - o2.getMax());
        }
    };

    protected final SmartSlf4jLogger logger = SmartSlf4jLoggerFactory.getLogger(getClass());

    private Object lock = new Object();

    private final Map> records = Colls.concurrentMap(32);

    private final MicrometerAdviceTimerInfo micrometerAdviceTimerInfo;

    public TimerHealthIndicator(MicrometerAdviceTimerInfo micrometerAdviceTimerInfo) {
        this.micrometerAdviceTimerInfo = micrometerAdviceTimerInfo;
    }

    @Override
    public void record(String metricName, long time) {
        int latestCount = micrometerAdviceTimerInfo.getSamples();
        logger.debug("Add times metric: {}, latestCount: {}, time={}", metricName, latestCount, time);

        Deque deque = records.get(metricName);
        if (null == deque) {
            synchronized (lock) {
                deque = records.get(metricName);
                if (null == deque) {
                    records.put(metricName, deque = Colls.concurrentDeque());
                }
            }
        }

        /*Overflow check.*/
        if (deque.size() >= (latestCount - 1)) {
            /*Remove first.*/
            deque.poll();
        }
        deque.offer(time);
    }

    @Override
    protected void doHealthCheck(Builder builder) throws Exception {
        try {
            /*Gets the statistical (MAX/MIN/AVG/LATEST/..).*/
            TimerStat stat = getLargestStat();

            if (null == stat) {
                HealthUtil.up(builder, "Healthy (No data).");
                return;
            }

            logger.debug("Times stat: {}", () -> JsonUtil.toJsonString(stat));

            if (stat.getMax() < micrometerAdviceTimerInfo.getTimeoutThresholdMills()) {
                HealthUtil.up(builder, "Healthy");
            } else {
                HealthUtil.down(builder,
                        new StringBuilder("Method ").append(stat.getMetricsName())
                                .append(" executes ")
                                .append(stat.getLatest())
                                .append("ms with a response exceeding the threshold value of ")
                                .append(micrometerAdviceTimerInfo.getTimeoutThresholdMills())
                                .append("ms.")
                                .toString());
                /*When the timeout exception is detected, the exception record is cleared.*/
                resetIfNecessary(stat);
            }
            builder.withDetail("Method", stat.getMetricsName())
                    .withDetail("Least", stat.getMin())
                    .withDetail("Largest", stat.getMax())
                    .withDetail("Avg", stat.getAvg())
                    .withDetail("Latest", stat.getLatest())
                    .withDetail("Samples", stat.getSamples())
                    .withDetail("Threshold", micrometerAdviceTimerInfo.getTimeoutThresholdMills() + "ms");
        } catch (Exception ex) {
            HealthUtil.down(builder, "UnHealthy", ex);
            logger.error("Failed to detected timeout.method.calling", ex);
        }
    }

    /**
     * Gets the most statistics largest time out messages (including average
     * time, maximum duration, and longest time) of `latestMeasureCount` call
     * records.
     * 
     * @return {@link TimerStat}
     */
    protected TimerStat getLargestStat() {
        List stats = new ArrayList<>();
        /*Calculate the maximum value of each execution record queue.*/
        for (Entry> ent : records.entrySet()) {
            Deque deque = ent.getValue();
            if (!deque.isEmpty()) {
                long queueMax = deque.stream().reduce(Long::max).get();
                stats.add(new TimerStat(ent.getKey(), queueMax));
            }
        }
        if (stats.isEmpty()) {
            return null;
        }

        /*Gets the maximum value in the list of the maximum values in all queues.*/
        TimerStat statMax = Collections.max(stats, MAX_COMPARATOR);
        Deque deque = records.get(statMax.getMetricsName());

        /*Average length of time.*/
        long totalTime = deque.stream().collect(Collectors.summingLong(Long::longValue));
        long count = deque.stream().count();

        statMax.setSamples(deque.size())
        .setMin(deque.stream().reduce(Long::min).get())
        .setLatest(deque.peekLast())
        .setAvg(BigDecimal.valueOf(totalTime / count).setScale(2, RoundingMode.HALF_EVEN).longValue());

        /*Remove current-largest metrics(timeouts record).*/
        deque.remove(statMax.getMax());
        //deque.clear();
        return statMax;
    }

    protected void resetIfNecessary(TimerStat stat) {
        Deque deque = records.get(stat.getMetricsName());
        if (deque != null && !deque.isEmpty()) {
            deque.remove(stat.getMax());
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy