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

io.helidon.grpc.metrics.GrpcMetrics Maven / Gradle / Ivy

There is a newer version: 4.0.0-ALPHA6
Show newest version
/*
 * Copyright (c) 2019, 2021 Oracle and/or its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.helidon.grpc.metrics;

import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;

import javax.annotation.Priority;

import io.helidon.common.LazyValue;
import io.helidon.grpc.core.GrpcHelper;
import io.helidon.grpc.core.InterceptorPriorities;
import io.helidon.grpc.server.MethodDescriptor;
import io.helidon.grpc.server.ServiceDescriptor;
import io.helidon.metrics.api.RegistryFactory;

import io.grpc.Context;
import io.grpc.ForwardingServerCall;
import io.grpc.Metadata;
import io.grpc.ServerCall;
import io.grpc.ServerCallHandler;
import io.grpc.ServerInterceptor;
import io.grpc.Status;
import org.eclipse.microprofile.metrics.ConcurrentGauge;
import org.eclipse.microprofile.metrics.Counter;
import org.eclipse.microprofile.metrics.Histogram;
import org.eclipse.microprofile.metrics.MetadataBuilder;
import org.eclipse.microprofile.metrics.Meter;
import org.eclipse.microprofile.metrics.MetricRegistry;
import org.eclipse.microprofile.metrics.MetricType;
import org.eclipse.microprofile.metrics.MetricUnits;
import org.eclipse.microprofile.metrics.SimpleTimer;
import org.eclipse.microprofile.metrics.Tag;
import org.eclipse.microprofile.metrics.Timer;

/**
 * A {@link io.grpc.ServerInterceptor} that enables capturing of gRPC call metrics.
 */
@Priority(InterceptorPriorities.TRACING + 1)
public class GrpcMetrics
        implements ServerInterceptor, ServiceDescriptor.Configurer, MethodDescriptor.Configurer {

    /**
     * The registry of vendor metrics.
     */
    static final LazyValue VENDOR_REGISTRY = LazyValue.create(() ->
            RegistryFactory.getInstance().getRegistry(MetricRegistry.Type.VENDOR));

    /**
     * The registry of application metrics.
     */
    static final LazyValue APP_REGISTRY = LazyValue.create(() ->
            RegistryFactory.getInstance().getRegistry(MetricRegistry.Type.APPLICATION));

    static final org.eclipse.microprofile.metrics.Metadata GRPC_METER = org.eclipse.microprofile.metrics.Metadata
            .builder()
            .withName("grpc.requests.meter")
            .withDisplayName("Meter for overall gRPC requests")
            .withDescription("Each gRPC request will mark the meter to see overall throughput")
            .withType(MetricType.METERED)
            .withUnit(MetricUnits.NONE)
            .build();

    /**
     * The context key name to use to obtain rules to use when applying metrics.
     */
    private static final String KEY_STRING = GrpcMetrics.class.getName();

    /**
     * The context key to use to obtain rules to use when applying metrics.
     */
    private static final Context.Key KEY = Context.keyWithDefault(KEY_STRING, new MetricsRules(MetricType.INVALID));

    /**
     * The metric rules to use.
     */
    private final MetricsRules metricRule;

    /**
     * Create a {@link GrpcMetrics}.
     *
     * @param rules  the metric rules to use
     */
    private GrpcMetrics(MetricsRules rules) {
        this.metricRule = rules;
    }

    @Override
    public void configure(MethodDescriptor.Rules rules) {
        rules.addContextValue(KEY, metricRule);
    }

    @Override
    public void configure(ServiceDescriptor.Rules rules) {
        rules.addContextValue(KEY, metricRule);
    }

    /**
     * Set the tags to apply to the metric.
     *
     * @param tags the tags to apply to the metric
     * @return a {@link io.helidon.grpc.metrics.GrpcMetrics} interceptor
     * @see org.eclipse.microprofile.metrics.Metadata
     */
    public GrpcMetrics tags(Map tags) {
        return new GrpcMetrics(metricRule.tags(tags));
    }

    /**
     * Set the description to apply to the metric.
     *
     * @param description the description to apply to the metric
     * @return a {@link io.helidon.grpc.metrics.GrpcMetrics} interceptor
     * @see org.eclipse.microprofile.metrics.Metadata
     */
    public GrpcMetrics description(String description) {
        return new GrpcMetrics(metricRule.description(description));
    }

    /**
     * Set the display name to apply to the metric.
     *
     * @param displayName the display name to apply to the metric
     * @return a {@link io.helidon.grpc.metrics.GrpcMetrics} interceptor
     * @see org.eclipse.microprofile.metrics.Metadata
     */
    public GrpcMetrics displayName(String displayName) {
        return new GrpcMetrics(metricRule.displayName(displayName));
    }

    /**
     * Set the units to apply to the metric.
     *
     * @param units the units to apply to the metric
     * @return a {@link io.helidon.grpc.metrics.GrpcMetrics} interceptor
     * @see org.eclipse.microprofile.metrics.Metadata
     */
    public GrpcMetrics units(String units) {
        return new GrpcMetrics(metricRule.units(units));
    }

    /**
     * Set the reusability of the metric.
     * @param reusable {@code true} if this metric may be reused
     * @return a {@link io.helidon.grpc.metrics.GrpcMetrics} interceptor
     * @see org.eclipse.microprofile.metrics.Metadata
     */
    public GrpcMetrics reusable(boolean reusable) {
        return new GrpcMetrics(metricRule.reusable(reusable));
    }

    /**
     * Obtain the {@link org.eclipse.microprofile.metrics.MetricType}.
     *
     * @return the {@link org.eclipse.microprofile.metrics.MetricType}
     */
    public MetricType metricType() {
        return metricRule.type();
    }

    /**
     * Set the {@link NamingFunction} to use to generate the metric name.
     * 

* The default name will be the {@code .}. * * @param function the function to use to create the metric name * @return a {@link io.helidon.grpc.metrics.GrpcMetrics} interceptor */ public GrpcMetrics nameFunction(NamingFunction function) { return new GrpcMetrics(metricRule.nameFunction(function)); } /** * A static factory method to create a {@link GrpcMetrics} instance * to count gRPC method calls. * * @return a {@link GrpcMetrics} instance to capture call counts */ public static GrpcMetrics counted() { return new GrpcMetrics(new MetricsRules(MetricType.COUNTER)); } /** * A static factory method to create a {@link GrpcMetrics} instance * to meter gRPC method calls. * * @return a {@link GrpcMetrics} instance to meter gRPC calls */ public static GrpcMetrics metered() { return new GrpcMetrics(new MetricsRules(MetricType.METERED)); } /** * A static factory method to create a {@link GrpcMetrics} instance * to create a histogram of gRPC method calls. * * @return a {@link GrpcMetrics} instance to create a histogram of gRPC method calls */ public static GrpcMetrics histogram() { return new GrpcMetrics(new MetricsRules(MetricType.HISTOGRAM)); } /** * A static factory method to create a {@link GrpcMetrics} instance * to time gRPC method calls. * * @return a {@link GrpcMetrics} instance to time gRPC method calls */ public static GrpcMetrics timed() { return new GrpcMetrics(new MetricsRules(MetricType.TIMER)); } /** * A static factory method to create a {@link GrpcMetrics} instance * to time gRPC method calls. * * @return a {@link GrpcMetrics} instance to time gRPC method calls */ public static GrpcMetrics concurrentGauge() { return new GrpcMetrics(new MetricsRules(MetricType.CONCURRENT_GAUGE)); } /** * A static factory method to create a {@link GrpcMetrics} instance * to time gRPC method calls. * * @return a {@link GrpcMetrics} instance to time gRPC method calls */ public static GrpcMetrics simplyTimed() { return new GrpcMetrics(new MetricsRules(MetricType.SIMPLE_TIMER)); } @Override public ServerCall.Listener interceptCall(ServerCall call, Metadata headers, ServerCallHandler next) { MetricsRules rules = Context.keyWithDefault(KEY_STRING, metricRule).get(); MetricType type = rules.type(); String fullMethodName = call.getMethodDescriptor().getFullMethodName(); String methodName = GrpcHelper.extractMethodName(fullMethodName); ServiceDescriptor service = ServiceDescriptor.SERVICE_DESCRIPTOR_KEY.get(); ServerCall serverCall; switch (type) { case COUNTER: serverCall = new CountedServerCall<>(APP_REGISTRY.get().counter( rules.metadata(service, methodName), rules.toTags()), call); break; case METERED: serverCall = new MeteredServerCall<>(APP_REGISTRY.get().meter( rules.metadata(service, methodName), rules.toTags()), call); break; case HISTOGRAM: serverCall = new HistogramServerCall<>(APP_REGISTRY.get().histogram( rules.metadata(service, methodName), rules.toTags()), call); break; case TIMER: serverCall = new TimedServerCall<>(APP_REGISTRY.get().timer( rules.metadata(service, methodName), rules.toTags()), call); break; case SIMPLE_TIMER: serverCall = new SimplyTimedServerCall<>(APP_REGISTRY.get().simpleTimer( rules.metadata(service, methodName), rules.toTags()), call); break; case CONCURRENT_GAUGE: serverCall = new ConcurrentGaugeServerCall<>(APP_REGISTRY.get().concurrentGauge( rules.metadata(service, methodName), rules.toTags()), call); break; case GAUGE: case INVALID: default: serverCall = call; } serverCall = new MeteredServerCall<>(VENDOR_REGISTRY.get().meter(GRPC_METER), serverCall); return next.startCall(serverCall, headers); } /** * A {@link io.grpc.ServerCall} that captures metrics for a gRPC call. * * @param the call request type * @param the call response type * @param the type of metric to capture */ private abstract class MetricServerCall extends ForwardingServerCall.SimpleForwardingServerCall { /** * The metric to update. */ private final MetricT metric; /** * Create a {@link TimedServerCall}. * * @param delegate the call to time */ private MetricServerCall(MetricT metric, ServerCall delegate) { super(delegate); this.metric = metric; } /** * Obtain the metric being tracked. * * @return the metric being tracked */ protected MetricT getMetric() { return metric; } } /** * A {@link GrpcMetrics.MeteredServerCall} that captures call times. * * @param the call request type * @param the call response type */ private class TimedServerCall extends MetricServerCall { /** * The method start time. */ private final long startNanos; /** * Create a {@link TimedServerCall}. * * @param delegate the call to time */ private TimedServerCall(Timer timer, ServerCall delegate) { super(timer, delegate); this.startNanos = System.nanoTime(); } @Override public void close(Status status, Metadata responseHeaders) { super.close(status, responseHeaders); long time = System.nanoTime() - startNanos; getMetric().update(time, TimeUnit.NANOSECONDS); } } /** * A {@link GrpcMetrics.MeteredServerCall} that captures call times. * * @param the call request type * @param the call response type */ private class SimplyTimedServerCall extends MetricServerCall { /** * The method start time. */ private final long startNanos; /** * Create a {@link SimplyTimedServerCall}. * * @param delegate the call to time */ private SimplyTimedServerCall(SimpleTimer simpleTimer, ServerCall delegate) { super(simpleTimer, delegate); this.startNanos = System.nanoTime(); } @Override public void close(Status status, Metadata responseHeaders) { super.close(status, responseHeaders); long time = System.nanoTime() - startNanos; getMetric().update(Duration.ofNanos(time)); } } /** * A {@link GrpcMetrics.MeteredServerCall} that captures call times. * * @param the call request type * @param the call response type */ private class ConcurrentGaugeServerCall extends MetricServerCall { /** * Create a {@link SimplyTimedServerCall}. * * @param delegate the call to time */ private ConcurrentGaugeServerCall(ConcurrentGauge concurrentGauge, ServerCall delegate) { super(concurrentGauge, delegate); } @Override public void close(Status status, Metadata responseHeaders) { super.close(status, responseHeaders); getMetric().inc(); } } @Override public boolean equals(Object o) { if (this == o) { return true; } return o != null && getClass() == o.getClass(); } @Override public int hashCode() { return getClass().hashCode(); } /** * A {@link GrpcMetrics.MeteredServerCall} that captures call counts. * * @param the call request type * @param the call response type */ private class CountedServerCall extends MetricServerCall { /** * Create a {@link CountedServerCall}. * * @param delegate the call to time */ private CountedServerCall(Counter counter, ServerCall delegate) { super(counter, delegate); } @Override public void close(Status status, Metadata responseHeaders) { super.close(status, responseHeaders); getMetric().inc(); } } /** * A {@link GrpcMetrics.MeteredServerCall} that meters gRPC calls. * * @param the call request type * @param the call response type */ private class MeteredServerCall extends MetricServerCall { /** * Create a {@link MeteredServerCall}. * * @param delegate the call to time */ private MeteredServerCall(Meter meter, ServerCall delegate) { super(meter, delegate); } @Override public void close(Status status, Metadata responseHeaders) { super.close(status, responseHeaders); getMetric().mark(); } } /** * A {@link GrpcMetrics.MeteredServerCall} that creates a histogram for gRPC calls. * * @param the call request type * @param the call response type */ private class HistogramServerCall extends MetricServerCall { /** * Create a {@link HistogramServerCall}. * * @param delegate the call to time */ private HistogramServerCall(Histogram histogram, ServerCall delegate) { super(histogram, delegate); } @Override public void close(Status status, Metadata responseHeaders) { super.close(status, responseHeaders); getMetric().update(1); } } /** * Implemented by classes that can create a metric name. */ @FunctionalInterface public interface NamingFunction { /** * Create a metric name. * * @param service the service descriptor * @param methodName the method name * @param metricType the metric type * @return the metric name */ String createName(ServiceDescriptor service, String methodName, MetricType metricType); } /** * An immutable holder of metrics information. *

* Calls made to mutating methods return a new instance * of {@link MetricsRules} with the mutation applied. */ @SuppressWarnings("OptionalUsedAsFieldOrParameterType") static class MetricsRules { private static final Tag[] EMPTY_TAGS = new Tag[0]; /** * The metric type. */ private MetricType type; /** * The tags of the metric. * * @see org.eclipse.microprofile.metrics.Metadata */ private Optional> tags = Optional.empty(); /** * The description of the metric. * * @see org.eclipse.microprofile.metrics.Metadata */ private Optional description = Optional.empty(); /** * The display name of the metric. * * @see org.eclipse.microprofile.metrics.Metadata */ private Optional displayName = Optional.empty(); /** * The unit of the metric. * * @see org.eclipse.microprofile.metrics.Metadata * @see org.eclipse.microprofile.metrics.MetricUnits */ private Optional units = Optional.empty(); /** * The reusability status of this metric. * @see org.eclipse.microprofile.metrics.Metadata */ private boolean reusable; /** * The function to use to obtain the metric name. */ private Optional nameFunction = Optional.empty(); private MetricsRules(MetricType type) { this.type = type; } private MetricsRules(MetricsRules copy) { this.type = copy.type; this.tags = copy.tags; this.description = copy.description; this.displayName = copy.displayName; this.units = copy.units; this.nameFunction = copy.nameFunction; this.reusable = copy.reusable; } /** * Obtain the metric type. * * @return the metric type */ MetricType type() { return type; } /** * Obtain the metrics metadata. * * @param service the service descriptor * @param method the method name * @return the metrics metadata */ org.eclipse.microprofile.metrics.Metadata metadata(ServiceDescriptor service, String method) { String name = nameFunction.orElse(this::defaultName).createName(service, method, type); MetadataBuilder builder = org.eclipse.microprofile.metrics.Metadata.builder() .withName(name) .withType(type) .reusable(this.reusable); this.description.ifPresent(builder::withDescription); this.units.ifPresent(builder::withUnit); this.displayName.ifPresent(builder::withDisplayName); return builder.build(); } private String defaultName(ServiceDescriptor service, String methodName, MetricType metricType) { return (service.name() + "." + methodName).replaceAll("/", "."); } private MetricsRules tags(Map tags) { MetricsRules rules = new MetricsRules(this); rules.tags = Optional.of(new HashMap<>(tags)); return rules; } private MetricsRules description(String description) { MetricsRules rules = new MetricsRules(this); rules.description = Optional.of(description); return rules; } private MetricsRules displayName(String displayName) { MetricsRules rules = new MetricsRules(this); rules.displayName = Optional.of(displayName); return rules; } private MetricsRules nameFunction(NamingFunction function) { MetricsRules rules = new MetricsRules(this); rules.nameFunction = Optional.of(function); return rules; } private MetricsRules units(String units) { MetricsRules rules = new MetricsRules(this); rules.units = Optional.of(units); return rules; } private MetricsRules reusable(boolean reusable) { MetricsRules rules = new MetricsRules(this); rules.reusable = reusable; return rules; } private Tag[] toTags() { return tags.isPresent() ? tags.get().entrySet().stream() .map(entry -> new Tag(entry.getKey(), entry.getValue())) .toArray(Tag[]::new) : EMPTY_TAGS; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy