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

org.kiwiproject.dropwizard.util.health.CachingHealthCheck Maven / Gradle / Ivy

There is a newer version: 4.0.1
Show newest version
package org.kiwiproject.dropwizard.util.health;

import static org.kiwiproject.base.KiwiPreconditions.requireNotBlank;
import static org.kiwiproject.base.KiwiPreconditions.requireNotNull;
import static org.kiwiproject.logging.LazyLogParameterSupplier.lazy;

import com.codahale.metrics.health.HealthCheck;
import com.codahale.metrics.health.HealthCheckRegistry;
import com.google.common.annotations.VisibleForTesting;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import lombok.Getter;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.kiwiproject.base.DefaultEnvironment;
import org.kiwiproject.base.KiwiEnvironment;
import org.kiwiproject.metrics.health.HealthCheckResults;

import java.time.Duration;
import java.util.concurrent.atomic.AtomicReference;

/**
 * A {@link HealthCheck} that caches the result of another HealthCheck
 * for a certain period of time.
 * 

* Use this if a HealthCheck is expensive to compute, and you want to minimize * the execution cost. For example, a HealthCheck that makes network calls might * be a good candidate for caching if health checks are executed frequently. */ @Slf4j public class CachingHealthCheck extends HealthCheck { /** * The default cache expiration, during which period the same Result will be returned (5 minutes). */ public static final Duration DEFAULT_CACHE_EXPIRATION_DURATION = Duration.ofMinutes(5); record TimestampedResult(long epochMillis, HealthCheck.Result result) { } /** * A name for this CachingHealthCheck. *

* Usually this should be the name given to the wrapped HealthCheck * so that it can be easily distinguished from others. */ @Getter @Accessors(fluent = true) private final String name; /** * The wrapped HealthCheck whose results will be cached. */ @Getter @Accessors(fluent = true) private final HealthCheck healthCheck; private final long cacheExpirationMillis; private final KiwiEnvironment kiwiEnvironment; private final AtomicReference lastResultReference; /** * Create a new instance with the default cache expiration. * * @param name the health check name * @param healthCheck the HealthCheck whose results will be cached * @see #DEFAULT_CACHE_EXPIRATION_DURATION */ public CachingHealthCheck(String name, HealthCheck healthCheck) { this(name, healthCheck, DEFAULT_CACHE_EXPIRATION_DURATION); } /** * Create a new instance. * * @param name the health check name * @param healthCheck the HealthCheck whose results will be cached * @param cacheExpiration the cache expiration period */ public CachingHealthCheck(String name, HealthCheck healthCheck, Duration cacheExpiration) { this(name, healthCheck, cacheExpiration, new DefaultEnvironment()); } @VisibleForTesting CachingHealthCheck(String name, HealthCheck healthCheck, Duration cacheExpiration, KiwiEnvironment kiwiEnvironment) { this.name = requireNotBlank(name); this.healthCheck = requireNotNull(healthCheck); this.cacheExpirationMillis = requireNotNull(cacheExpiration).toMillis(); this.kiwiEnvironment = kiwiEnvironment; this.lastResultReference = new AtomicReference<>(new TimestampedResult(0, null)); } /** * Create and register a new CachingHealthCheck that caches for the given HealthCheck * using the default cache expiration. * * @param registry the health check registry * @param name the health check name * @param healthCheck the HealthCheck whose results will be cached * @return a new CachingHealthCheck instance * @see #DEFAULT_CACHE_EXPIRATION_DURATION */ @CanIgnoreReturnValue public static CachingHealthCheck register(HealthCheckRegistry registry, String name, HealthCheck healthCheck) { return register(registry, name, healthCheck, DEFAULT_CACHE_EXPIRATION_DURATION); } /** * Create and register a new CachingHealthCheck that caches for the given HealthCheck. * * @param registry the health check registry * @param name the health check name * @param healthCheck the HealthCheck whose results will be cached * @param cacheExpiration the cache expiration period * @return a new CachingHealthCheck instance */ @CanIgnoreReturnValue public static CachingHealthCheck register(HealthCheckRegistry registry, String name, HealthCheck healthCheck, Duration cacheExpiration) { var cachingHealthCheck = new CachingHealthCheck(name, healthCheck, cacheExpiration); registry.register(name, cachingHealthCheck); return cachingHealthCheck; } /** * If the time since the last check exceeds the cache expiration, * then execute the wrapped HealthCheck and return its Result. *

* Otherwise, return the last Result. */ @Override protected Result check() throws Exception { var nowEpochMillis = kiwiEnvironment.currentTimeMillis(); var lastResult = lastResultReference.get(); var millisSinceLastCheck = nowEpochMillis - lastResult.epochMillis(); if (millisSinceLastCheck < cacheExpirationMillis) { logTimeSinceLastCheck(millisSinceLastCheck); return lastResult.result(); } LOG.debug("Time since last check ({} millis) is greater than cache expiration ({} millis). Do check.", millisSinceLastCheck, cacheExpirationMillis); var newResult = executeHealthCheck(); lastResultReference.set(newResult); return newResult.result(); } private void logTimeSinceLastCheck(long millisSinceLastCheck) { LOG.debug("Time since last check ({} millis) is less than cache expiration ({} millis)." + " Time until next check: {} millis. Returning last result.", millisSinceLastCheck, cacheExpirationMillis, lazy(() -> cacheExpirationMillis - millisSinceLastCheck) ); } private TimestampedResult executeHealthCheck() { // The default HealthCheck#execute implementation catches Exception, but it // is not final and can therefore be overridden. So, we will be conservative // and assume an Exception could be thrown in a subclass. Result result; try { result = healthCheck.execute(); } catch (Exception e) { var healthCheckClassName = healthCheck.getClass().getName(); LOG.warn("'{}' - {}#execute unexpectedly threw an exception", name, healthCheckClassName, e); result = HealthCheckResults.newUnhealthyResult(e, "'%s' - %s#execute unexpectedly threw an exception. Exception: %s, Message: %s", name, healthCheckClassName, e.getClass().getName(), e.getMessage()); } return new TimestampedResult(kiwiEnvironment.currentTimeMillis(), result); } /** * @return the cache expiration period of this CachingHealthCheck */ public Duration cacheExpiration() { return Duration.ofMillis(cacheExpirationMillis); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy