com.github.strengthened.prometheus.healthchecks.HealthChecksCollector Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of prometheus-healthchecks Show documentation
Show all versions of prometheus-healthchecks Show documentation
Metrics collector for Healthcheck system.
The newest version!
package com.github.strengthened.prometheus.healthchecks;
import io.prometheus.client.Collector;
import io.prometheus.client.GaugeMetricFamily;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* A Collector for health checks.
*/
public class HealthChecksCollector extends Collector {
final String gaugeMetricHelp;
final String gaugeMetricName;
final List labelNames;
private final ConcurrentMap healthChecks;
private final ScheduledExecutorService asyncExecutorService;
private final Object lock;
private HealthChecksCollector(String gaugeMetricHelp, String gaugeMetricName, String labelName,
ScheduledExecutorService asyncExecutorService) {
super();
this.gaugeMetricHelp = gaugeMetricHelp;
this.gaugeMetricName = gaugeMetricName;
this.asyncExecutorService = asyncExecutorService;
this.healthChecks = new ConcurrentHashMap();
this.labelNames = Collections.singletonList(labelName);
this.lock = new Object();
}
/**
* Builds a new instance of {@code HealthChecksCollector} with default values, except for the
* custom ScheduledExecutorService.
*
* @param asyncExecutorService the custom {@code ScheduledExecutorService}
* @see Builder#setAsyncExecutorService(ScheduledExecutorService)
* @return a new instance of {@code HealthChecksCollector}.
*/
public static HealthChecksCollector newInstance(ScheduledExecutorService asyncExecutorService) {
return Builder.of().setAsyncExecutorService(asyncExecutorService).build();
}
/**
* Builds a new instance of {@code HealthChecksCollector} with default values, except for the
* custom Pool Size value.
*
* @param asyncExecutorPoolSize the custom number of threads to keep in the pool, even if they are
* idle.
* @see Builder#setAsyncExecutorPoolSize(int)
* @return a new instance of {@code HealthChecksCollector}.
*/
public static HealthChecksCollector newInstance(int asyncExecutorPoolSize) {
return Builder.of().setAsyncExecutorPoolSize(asyncExecutorPoolSize).build();
}
/**
* Builds a new instance of {@code HealthChecksCollector} with default values.
*
* @see Builder#of()
* @return a new instance of {@code HealthChecksCollector}.
*/
public static HealthChecksCollector newInstance() {
return Builder.of().build();
}
@Override
public List collect() {
if (healthChecks.isEmpty()) {
return Collections.emptyList();
} else {
GaugeMetricFamily checks =
new GaugeMetricFamily(gaugeMetricName, gaugeMetricHelp, labelNames);
for (Entry entry : healthChecks.entrySet()) {
final String name = entry.getKey();
final HealthCheck healthCheck = entry.getValue();
final HealthStatus result = healthCheck.execute();
checks.addMetric(Collections.singletonList(name), result.value);
}
return Collections.singletonList(checks);
}
}
/**
* Link an application {@link HealthCheck}.
*
* @param name the name of the health check
* @param healthCheck the {@link HealthCheck} instance
* @return a modified instance of this {@code HealthChecksCollector}.
* @throws IllegalArgumentException if the {@link HealthCheck} instance is already linked
*/
public HealthChecksCollector addHealthCheck(String name, HealthCheck healthCheck) {
synchronized (lock) {
if (healthChecks.containsKey(requireNonNull(name, "name null"))) {
throw new IllegalArgumentException("A health check named " + name + " already exists");
}
HealthCheck linked = requireNonNull(healthCheck, "healthCheck null");
if (healthCheck.getClass().isAnnotationPresent(Async.class)) {
linked = new AsyncHealthCheckDecorator(healthCheck, asyncExecutorService);
}
healthChecks.put(name, linked);
}
return this;
}
/**
* Link a collection of application {@link HealthCheck}.
*
* @param healthChecks the {@code name} - {@link HealthCheck} map
* @param any type extending {@link HealthCheck}
* @return a modified instance of this {@code HealthChecksCollector}.
* @throws IllegalArgumentException if a {@link HealthCheck} instance is already linked
*/
public HealthChecksCollector addHealthChecks(
Map healthChecks) {
for (Entry healthCheck : requireNonNull(healthChecks, "healthChecks null")
.entrySet()) {
addHealthCheck(healthCheck.getKey(), healthCheck.getValue());
}
return this;
}
/**
* Unlink an application {@link HealthCheck}.
*
* @param name the name of the health check
* @return a modified instance of this {@code HealthChecksCollector}.
*/
public HealthChecksCollector removeHealthCheck(String name) {
synchronized (lock) {
final HealthCheck healthCheck = healthChecks.remove(name);
if (healthCheck instanceof AsyncHealthCheckDecorator) {
((AsyncHealthCheckDecorator) healthCheck).tearDown();
}
}
return this;
}
/**
* Unlink all the linked {@link HealthCheck}.
*
* @return a modified instance of this {@code HealthChecksCollector}.
*/
public HealthChecksCollector clear() {
for (String key : healthChecks.keySet()) {
removeHealthCheck(key);
}
shutdown();
return this;
}
/**
* Returns a set of the names of all linked health checks.
*
* @return the names of all linked health checks
*/
public SortedSet getNames() {
return Collections.unmodifiableSortedSet(new TreeSet(healthChecks.keySet()));
}
/**
* Returns an {@link HealthCheck} of a given name, or {@code null} if this Collector contains no
* mapping for the name.
*
* @param name the name of the health check
* @return the {@link HealthCheck} instance, or {@code null}
*/
public HealthCheck getHealthCheck(String name) {
return healthChecks.get(requireNonNull(name, "name null"));
}
/**
* Runs the health check with the given name.
*
* @param name the health check's name
* @return the result of the health check
* @throws NoSuchElementException if there is no health check with the given name
*/
public HealthStatus runHealthCheck(String name) {
final HealthCheck healthCheck = healthChecks.get(name);
if (healthCheck == null) {
throw new NoSuchElementException("No health check named " + name + " exists");
}
return healthCheck.execute();
}
/**
* Shuts down the scheduled executor for async health checks
*
* @return a modified instance of this {@code HealthChecksCollector}.
*/
public HealthChecksCollector shutdown() {
asyncExecutorService.shutdown();
try {
if (!asyncExecutorService.awaitTermination(1, TimeUnit.SECONDS)) {
asyncExecutorService.shutdownNow();
}
} catch (InterruptedException ie) {
asyncExecutorService.shutdownNow();
Thread.currentThread().interrupt();
}
return this;
}
private static T requireNonNull(T obj, String string) {
if (obj == null) {
throw new NullPointerException(string);
}
return obj;
}
/**
* Builds instances of type {@link HealthChecksCollector HealthChecksCollector}. Initialize
* attributes and then invoke the {@link #build()} method to create an immutable instance.
*
* {@code Builder} is not thread-safe and generally should not be stored in a field or
* collection, but instead used immediately to create instances.
*/
public static final class Builder {
private static final int ASYNC_EXECUTOR_POOL_SIZE = 2;
private String gaugeMetricHelp = "Health check status results";
private String gaugeMetricName = "health_check_status";
private String gaugeMetricLabelName = "system";
private ScheduledExecutorService asyncExecutorService =
createExecutorService(ASYNC_EXECUTOR_POOL_SIZE);
/**
* Creates a new {@code Builder} instance with default values.
*
* - Metric Help = {@code Health check status results}
* - Metric Name = {@code health_check_status}
* - Metric Label = {@code system}
* - ExecutorService = {@code ScheduledExecutorService} with thread pool size of
* {@code 2}
*
*
* @return {@code this} builder for use in a chained invocation
*/
public static Builder of() {
return new Builder();
}
/**
* Fill a builder with attribute values from the provided {@code HealthChecksCollector}
* instance.
*
* @param instance The instance from which to copy values
* @return {@code this} builder for use in a chained invocation
*/
public Builder from(HealthChecksCollector instance) {
requireNonNull(instance, "instance");
setGaugeMetricHelp(instance.gaugeMetricHelp);
setGaugeMetricName(instance.gaugeMetricName);
setGaugeMetricLabelName(instance.labelNames.get(0));
setAsyncExecutorService(instance.asyncExecutorService);
return this;
}
/**
* Builds a new {@link HealthChecksCollector HealthChecksCollector}.
*
* @return An instance of HealthChecksCollector
*/
public HealthChecksCollector build() {
return new HealthChecksCollector(gaugeMetricHelp, gaugeMetricName, gaugeMetricLabelName,
asyncExecutorService);
}
/**
* Initializes the value for the gauge metric's help attribute.
*
* @param gaugeMetricHelp The value for gaugeMetricHelp
* @return {@code this} builder for use in a chained invocation
*/
public Builder setGaugeMetricHelp(String gaugeMetricHelp) {
this.gaugeMetricHelp = requireNonNull(gaugeMetricHelp, "gaugeMetricHelp null");
return this;
}
/**
* Initializes the value for the gauge metric's name attribute.
*
* @param gaugeMetricName The value for gaugeMetricName
* @return {@code this} builder for use in a chained invocation
*/
public Builder setGaugeMetricName(String gaugeMetricName) {
this.gaugeMetricName = requireNonNull(gaugeMetricName, "gaugeMetricName null");
return this;
}
/**
* Initializes the value for the gauge metric's label attribute.
*
* @param gaugeMetricLabelName The value for labelName
* @return {@code this} builder for use in a chained invocation
*/
public Builder setGaugeMetricLabelName(String gaugeMetricLabelName) {
this.gaugeMetricLabelName = requireNonNull(gaugeMetricLabelName, "gaugeMetricLabelName null");
return this;
}
/**
* Builds and initialize an {@code ScheduledExecutorService} with the specified pool size value.
*
* @param asyncExecutorPoolSize The value for asyncExecutorPoolSize
* @return {@code this} builder for use in a chained invocation
*/
public Builder setAsyncExecutorPoolSize(int asyncExecutorPoolSize) {
this.asyncExecutorService = createExecutorService(asyncExecutorPoolSize);
return this;
}
/**
* Initializes the value for the {@code ScheduledExecutorService}.
*
* @param asyncExecutorService The value for asyncExecutorService
* @return {@code this} builder for use in a chained invocation
*/
public Builder setAsyncExecutorService(ScheduledExecutorService asyncExecutorService) {
this.asyncExecutorService = requireNonNull(asyncExecutorService, "asyncExecutorService null");
return this;
}
private static ScheduledExecutorService createExecutorService(int corePoolSize) {
ScheduledExecutorService asyncExecutorService =
Executors.newScheduledThreadPool(corePoolSize, Executors.defaultThreadFactory());
try {
Method method =
asyncExecutorService.getClass().getMethod("setRemoveOnCancelPolicy", Boolean.TYPE);
method.invoke(asyncExecutorService, true);
} catch (NoSuchMethodException ex) {
logSetExecutorCancellationPolicyFailure(ex);
} catch (IllegalAccessException ex) {
logSetExecutorCancellationPolicyFailure(ex);
} catch (InvocationTargetException ex) {
logSetExecutorCancellationPolicyFailure(ex);
}
return asyncExecutorService;
}
private static void logSetExecutorCancellationPolicyFailure(Exception ex) {
System.err.println(String.format(
"Tried but failed to set executor cancellation policy to remove on cancel "
+ "which has been introduced in Java 7. This could result in a memory leak "
+ "if many asynchronous health checks are registered and removed because "
+ "cancellation does not actually remove them from the executor. %s",
ex.getMessage()));
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy