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

com.sproutsocial.metrics.Instrumentor Maven / Gradle / Ivy

package com.sproutsocial.metrics;

import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.function.Predicate;

import com.codahale.metrics.Counter;
import com.codahale.metrics.Gauge;
import com.codahale.metrics.Meter;
import com.codahale.metrics.Metered;
import com.codahale.metrics.MetricRegistry;
import com.codahale.metrics.Timer;
import com.codahale.metrics.health.HealthCheck;
import com.codahale.metrics.health.HealthCheckRegistry;
import com.google.common.base.Throwables;
import com.sproutsocial.metrics.gauges.Gauges;
import com.sproutsocial.metrics.healthchecks.HealthChecks;

/**
 * Created on 4/17/15
 *
 * @author horthy
 */
public class Instrumentor {

    /* package */ static final double NO_THRESHOLD_DEFINED = -1d;

    private final MetricRegistry metricRegistry;
    private final HealthCheckRegistry healthCheckRegistry;
    private final Predicate exceptionFilter;


    private class Context {
        private Meter errorMeter;
        private Timer timer;
        private Counter inFlight;

        Context(String name) {
            errorMeter = metricRegistry.meter(name + ".errors");
            timer = metricRegistry.timer(name);
            inFlight = metricRegistry.counter(name + ".inFlight");
        }
    }

    public static class Builder {
        private MetricRegistry metricRegistry = new MetricRegistry();
        private HealthCheckRegistry healthCheckRegistry = null;
        private Predicate filter = ExceptionFilters.markAllExceptions();

        private Builder() {}

        public Builder metricRegistry(MetricRegistry metricRegistry) {
            this.metricRegistry = metricRegistry;
            return this;
        }

        public Builder healthCheckRegistry(HealthCheckRegistry healthCheckRegistry) {
            this.healthCheckRegistry = healthCheckRegistry;
            return this;
        }

        public Builder exceptionFilter(Predicate filter) {
            this.filter = filter;
            return this;
        }

        public Instrumentor build() {
            return new Instrumentor(metricRegistry, healthCheckRegistry, filter);
        }
    }

    public static Builder builder() {
        return new Builder();
    }

    public Instrumentor() {
        this(new MetricRegistry(), new HealthCheckRegistry(), any -> true);
    }

    public Instrumentor(
            MetricRegistry metricRegistry,
            HealthCheckRegistry healthCheckRegistry,
            Predicate exceptionFilter) {
        this.metricRegistry = metricRegistry;
        this.healthCheckRegistry = healthCheckRegistry;
        this.exceptionFilter = exceptionFilter;
    }

    /**
     * @return the underlying {@link HealthCheckRegistry}
     */
    public HealthCheckRegistry getHealthCheckRegistry() {
        return healthCheckRegistry;
    }

    /**
     * @return the underlying {@link MetricRegistry}
     */
    public MetricRegistry getMetricRegistry() {
        return metricRegistry;
    }

    private  Callable instrumenting(
            Callable callable,
            String name,
            Optional errorThreshold
    ) {

        final Context context = createInstrumentationContext(name, errorThreshold);

        return () -> {
            context.inFlight.inc();
            try (@SuppressWarnings("unused") Timer.Context ctx = context.timer.time()){
                return callable.call();
            } catch (Exception e) {
                if (exceptionFilter.test(e)) {
                    context.errorMeter.mark();
                }
                throw e;
            } finally {
                context.inFlight.dec();
            }
        };
    }

    private  ThrowableCallable instrumenting(
            ThrowableCallable callable,
            String name,
            Optional errorThreshold
    ) {

        final Context context = createInstrumentationContext(name, errorThreshold);

        return () -> {
            context.inFlight.inc();
            try (@SuppressWarnings("unused") Timer.Context ctx = context.timer.time()){
                return callable.call();
            } catch (Throwable e) {
                if (exceptionFilter.test(e)) {
                    context.errorMeter.mark();
                }
                throw e;
            } finally {
                context.inFlight.dec();
            }
        };
    }

    private CheckedRunnable instrumenting(
            CheckedRunnable runnable,
            String name,
            Optional errorThreshold
    ) {

        final Context context = createInstrumentationContext(name, errorThreshold);

        return () -> {
            context.inFlight.inc();
            try (@SuppressWarnings("unused") Timer.Context ctx = context.timer.time()){
                runnable.run();
            } catch (Exception e) {
                if (exceptionFilter.test(e)) {
                    context.errorMeter.mark();
                }
                throw e;
            } finally {
                context.inFlight.dec();
            }
        };
    }

    private Runnable instrumenting(
            Runnable runnable,
            String name,
            Optional errorThreshold
    ) {

        final Context context = createInstrumentationContext(name, errorThreshold);

        return () -> {
            context.inFlight.inc();
            try (@SuppressWarnings("unused") Timer.Context ctx = context.timer.time()){
                runnable.run();
            } catch (Exception e) {
                if (exceptionFilter.test(e)) {
                    context.errorMeter.mark();
                }
                throw e;
            } finally {
                context.inFlight.dec();
            }
        };
    }

    private Context createInstrumentationContext(String name, Optional errorThreshold) {
        final Context context = new Context(name);
        if (!errorGaugesExist(name)) {
            registerErrorGauges(name, context.errorMeter, context.timer);
        }

        if (shouldRegisterHealthCheck(name, errorThreshold)) {
            registerHealthCheck(name, errorThreshold, context.errorMeter, context.timer);
        }
        return context;
    }

    private  boolean shouldRegisterHealthCheck(String name, Optional ceiling) {
        return healthCheckRegistry != null &&
                ceiling.isPresent() &&
                !healthCheckExists(name);
    }

    private boolean healthCheckExists(String name) {
        return healthCheckRegistry.getNames().contains(name);
    }

    private boolean errorGaugesExist(String name) {
        return metricRegistry
                .getGauges()
                .containsKey(MetricRegistry.name(name, "errors", "mean_pct"));
    }

    private void registerHealthCheck(String name, Optional ceiling, Metered errorMeter, Metered timer) {
        final Gauge errorRate = Gauges.ratioOf(errorMeter, timer, Metered::getFifteenMinuteRate);

        final HealthCheck healthCheck = HealthChecks.forDoubleGauge(
                errorRate,
                ceiling
        );

        healthCheckRegistry.register(name, healthCheck);
    }

    private void registerErrorGauges(String name, Meter errorMeter, Timer timer) {
        final Gauge totalErrorPct = Gauges.ratioOf(errorMeter, timer, m -> Long.valueOf(m.getCount()).doubleValue());
        final Gauge meanErrorPct = Gauges.ratioOf(errorMeter, timer, Metered::getMeanRate);
        final Gauge m1ErrorPct = Gauges.ratioOf(errorMeter, timer, Metered::getOneMinuteRate);
        final Gauge m5ErrorPct = Gauges.ratioOf(errorMeter, timer, Metered::getFiveMinuteRate);
        final Gauge m15ErrorPct = Gauges.ratioOf(errorMeter, timer, Metered::getFifteenMinuteRate);

        tryRegister(totalErrorPct, MetricRegistry.name(name, "errors", "total_pct"));
        tryRegister(meanErrorPct, MetricRegistry.name(name, "errors", "mean_pct"));
        tryRegister(m1ErrorPct, MetricRegistry.name(name, "errors", "m1_pct"));
        tryRegister(m5ErrorPct, MetricRegistry.name(name, "errors", "m5_pct"));
        tryRegister(m15ErrorPct, MetricRegistry.name(name, "errors", "m15_pct"));
    }

    /**
     * There's a Potential race condition with
     * the check to {@link this#errorGaugesExist(String)} and when
     * we actually go to register the gauge, so lets be extra careful here
     * and put it in a try-catch.
     *
     * This is a little hacky.
     */
    private void tryRegister(Gauge meanErrorRate, String name) {
        try {
            metricRegistry.register(name, meanErrorRate);
        } catch (IllegalArgumentException ignoreAlreadyRegistered)  {}
    }

    public  Callable instrumenting(
            Callable callable,
            String name,
            double errorThreshold
    ) {
        return instrumenting(callable, name, Optional.of(errorThreshold));
    }

    public CheckedRunnable instrumenting(
            CheckedRunnable runnable,
            String name,
            double errorThreshold
    ) {
        return instrumenting(runnable, name, Optional.of(errorThreshold));
    }

    public Runnable instrumenting(
            Runnable runnable,
            String name,
            double errorThreshold
    ) {
        return instrumenting(runnable, name, Optional.of(errorThreshold));
    }

    private void run(
            Runnable runnable,
            String name,
            Optional errorThreshold
    ) {
        instrumenting(runnable, name, errorThreshold).run();
    }

    private void runChecked(
            CheckedRunnable runnable,
            String name,
            Optional errorThreshold
    ) throws Exception {
        instrumenting(runnable, name, errorThreshold).run();
    }

    private  T call(
            Callable callable,
            String name,
            Optional errorThreshold
    ) {
        try {
            return instrumenting(callable, name, errorThreshold).call();
        } catch (Exception e) {
            throw Throwables.propagate(e);
        }
    }

    private  T callChecked(
            Callable callable,
            String name,
            Optional errorThreshold
    ) throws Exception {
        return instrumenting(callable, name, errorThreshold).call();
    }


    /* package */  T callThrowably(
            ThrowableCallable callable,
            String name,
            Optional errorThreshold
    ) throws Throwable {
        return instrumenting(callable, name, errorThreshold).call();
    }

    public void run(
            Runnable runnable,
            String name
    ) {
        run(runnable, name, Optional.empty());
    }

    public void runChecked(
            CheckedRunnable runnable,
            String name
    ) throws Exception {
        runChecked(runnable, name, Optional.empty());
    }

    public  T call(
            Callable callable,
            String name
    ) {
        return call(callable, name, Optional.empty());
    }

    public  T callChecked(
            Callable callable,
            String name
    ) throws Exception {
        return callChecked(callable, name, Optional.empty());
    }

    /* package */  T callThrowably(
            ThrowableCallable callable,
            String name
    ) throws Throwable {
        return callThrowably(callable, name, Optional.empty());
    }


    public void run(
            Runnable runnable,
            String name,
            double errorThreshold
    ) {
        run(runnable, name, Optional.of(errorThreshold));
    }

    public void runChecked(
            CheckedRunnable runnable,
            String name,
            double errorThreshold
    ) throws Exception {
        runChecked(runnable, name, Optional.of(errorThreshold));
    }

    public  T call(
            Callable callable,
            String name,
            double errorThreshold
    ) {
        return call(callable, name, Optional.of(errorThreshold));
    }

    public  T callChecked(
            Callable callable,
            String name,
            double errorThreshold
    ) throws Exception {
        return callChecked(callable, name, Optional.of(errorThreshold));
    }

    /* package */  T callThrowably(
            ThrowableCallable callable,
            String name,
            double errorThreshold
    ) throws Throwable {
        return callThrowably(callable, name, Optional.of(errorThreshold));
    }




}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy