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

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

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

import static java.util.Collections.emptyList;
import static java.util.function.Function.identity;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toMap;
import static java.util.stream.Collectors.toSet;
import static org.kiwiproject.base.KiwiPreconditions.checkArgument;
import static org.kiwiproject.base.KiwiPreconditions.requireNotNull;
import static org.kiwiproject.metrics.health.HealthCheckResults.newHealthyResult;
import static org.kiwiproject.metrics.health.HealthCheckResults.newResultBuilder;

import com.codahale.metrics.Gauge;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.health.HealthCheck;
import com.google.common.annotations.VisibleForTesting;
import lombok.Builder;
import lombok.Value;
import lombok.extern.slf4j.Slf4j;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;

/**
 * Health check that checks the percent of leased connections against the maximum number of connections for
 * JAX-RS {@link jakarta.ws.rs.client.Client} instances that were created using Dropwizard's
 * {@code io.dropwizard.client.JerseyClientBuilder}, which creates an HTTP connection pool and registers various
 * connection metrics that we can query.
 * 

* Please note, if using a default JAX-RS {@link jakarta.ws.rs.client.Client} created using the normal * {@link jakarta.ws.rs.client.ClientBuilder}, those clients will not have metrics registered and will therefore * not be included by this check (since it won't know about them). */ @Slf4j public class HttpConnectionsHealthCheck extends HealthCheck { /** * A default name that can be used when registering this health check. */ @SuppressWarnings("unused") public static final String DEFAULT_NAME = "HTTP Connections"; /** * Default percent above which this health check will report unhealthy. */ public static final double DEFAULT_WARNING_THRESHOLD = 50.0; private static final String HTTP_CONN_MANAGER_GAUGE_PREFIX = "org.apache.http.conn.HttpClientConnectionManager."; private static final int START_INDEX = HTTP_CONN_MANAGER_GAUGE_PREFIX.length(); private final MetricRegistry metrics; private final double warningThreshold; public HttpConnectionsHealthCheck(MetricRegistry metrics) { this(metrics, DEFAULT_WARNING_THRESHOLD); } public HttpConnectionsHealthCheck(MetricRegistry metrics, double warningThreshold) { this.metrics = requireNotNull(metrics); checkArgument(warningThreshold > 0.0 && warningThreshold < 100.0, IllegalArgumentException.class, "warningThreshold must be more than 0 and less than 100"); this.warningThreshold = warningThreshold; } @Override protected Result check() { var httpMetrics = metrics.getGauges((name, metric) -> name.startsWith(HTTP_CONN_MANAGER_GAUGE_PREFIX)); var clientNames = findHttpClientMetricNames(httpMetrics); LOG.trace("Client names: {}", clientNames); if (clientNames.isEmpty()) { return newHealthyResult("No HTTP clients found with metrics"); } var clientHealth = getClientHealth(httpMetrics, clientNames); return determineResult(clientHealth, clientNames.size()); } @SuppressWarnings("rawtypes") private static Set findHttpClientMetricNames(SortedMap httpMetrics) { return httpMetrics.keySet().stream() .map(HttpConnectionsHealthCheck::clientNameFrom) .collect(toSet()); } private static String clientNameFrom(String gaugeName) { return gaugeName.substring(START_INDEX, gaugeName.lastIndexOf('.')); } @SuppressWarnings("rawtypes") private Map> getClientHealth( SortedMap httpMetrics, Set clientNames) { return clientNames.stream() .map(clientName -> getClientConnectionInfo(httpMetrics, clientName)) .collect(groupingBy(ClientConnectionInfo::getHealthStatus)); } @SuppressWarnings("rawtypes") private ClientConnectionInfo getClientConnectionInfo(SortedMap httpMetrics, String clientName) { var leasedConnectionsGauge = httpMetrics.get(leasedConnectionsGaugeName(clientName)); var maxConnectionsGauge = httpMetrics.get(maxConnectionsGaugeName(clientName)); var leasedConnections = (int) leasedConnectionsGauge.getValue(); var maxConnections = (int) maxConnectionsGauge.getValue(); var connectionInfo = ClientConnectionInfo.builder() .clientName(clientName) .leased(leasedConnections) .max(maxConnections) .warningThreshold(warningThreshold) .build(); LOG.trace("{}: {} of {} leased ({}%)", clientName, leasedConnections, maxConnections, connectionInfo.percentLeased); return connectionInfo; } private Result determineResult(Map> clientHealth, int totalNumberOfClients) { var healthyClients = getConnectionInfoMap(clientHealth, ClientConnectionInfo.HealthStatus.HEALTHY); var unhealthyClients = getConnectionInfoMap(clientHealth, ClientConnectionInfo.HealthStatus.UNHEALTHY); var isHealthy = unhealthyClients.isEmpty(); var builder = newResultBuilder(isHealthy) .withDetail("healthyClients", healthyClients) .withDetail("unhealthyClients", unhealthyClients); if (isHealthy) { return builder .withMessage("%d HTTP client(s) < %4.1f%% leased connections.", totalNumberOfClients, warningThreshold) .build(); } LOG.trace("Unhealthy clients: {}", unhealthyClients); return builder .withMessage("%d of %d HTTP client(s) >= %4.1f%% leased connections.", unhealthyClients.size(), totalNumberOfClients, warningThreshold) .build(); } private Map getConnectionInfoMap( Map> clientHealth, ClientConnectionInfo.HealthStatus healthStatus) { return clientHealth.getOrDefault(healthStatus, emptyList()).stream() .collect(toMap(ClientConnectionInfo::getClientName, identity())); } private static String leasedConnectionsGaugeName(String clientName) { return httpConnectionGaugeName(clientName, ".leased-connections"); } private static String maxConnectionsGaugeName(String clientName) { return httpConnectionGaugeName(clientName, ".max-connections"); } private static String httpConnectionGaugeName(String clientName, String type) { return HTTP_CONN_MANAGER_GAUGE_PREFIX + clientName + type; } @Value @VisibleForTesting static class ClientConnectionInfo { String clientName; int leased; int max; double warningThreshold; double percentLeased; enum HealthStatus { HEALTHY, UNHEALTHY } @Builder ClientConnectionInfo(String clientName, int leased, int max, double warningThreshold) { this.clientName = clientName; this.leased = leased; this.max = max; this.warningThreshold = warningThreshold; this.percentLeased = 100.0 * (leased / (double) max); } boolean isUnhealthy() { return percentLeased >= warningThreshold; } HealthStatus getHealthStatus() { return isUnhealthy() ? HealthStatus.UNHEALTHY : HealthStatus.HEALTHY; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy