org.zodiac.actuate.health.checker.TimerHealthIndicator Maven / Gradle / Ivy
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