Skip to content

Commit

Permalink
Add GrpcMeterIdPrefixFunction (#2971)
Browse files Browse the repository at this point in the history
Motivation:
It'd be nice to have a dedicated `MeterIdPrefixFunction` implementation for gRPC,
so that the tag for grpc-status is added automatically.

Modifications:
- Add `GrpcMeterIdPrefixFunction`.
- Insert `GrpcWebTrailersExtrator` before `HttpClientDelegate` when the gRPC-web client is created.
- Remove `GrpcWebUtil.parseTrailers()` which was used to extract web trailers from RetringClient.
  - Could use `GrpcWebTrailers.get(ctx)` to get gRPC web trailers instead.
- Add `DefaultMeterIdPrefixFunction` which `GrpcMeterIdPrefixFunction` and `RetrofitMeterIdPrefixFunction` extend.
- Add `AbstractClientOptionsBuilder.clearDecorator()` to remove decorators set.
- Add meter tags in the Alphabet order.
- Add grpc-status header to the unframed grpc service response headers.

Result:
- Close #2762
- You can now use `GrpcMeterIdPrefixFunction` to add `grpc.status` tag to the metric easily.

To-do:
- The marking of successes and failures is not working well when
  `MetricCollectingClient` is between `RetryingClient` and `HttpClientDelegate`.
  • Loading branch information
minwoox committed Aug 13, 2020
1 parent a5ed773 commit 1929ba0
Show file tree
Hide file tree
Showing 33 changed files with 993 additions and 357 deletions.
Expand Up @@ -236,6 +236,14 @@ public AbstractClientOptionsBuilder decorator(DecoratingHttpClientFunction decor
return this;
}

/**
* Clears all HTTP-level and RPC-level decorators set so far.
*/
public AbstractClientOptionsBuilder clearDecorators() {
decoration.clear();
return this;
}

/**
* Adds the specified RPC-level {@code decorator}.
*
Expand Down
Expand Up @@ -197,6 +197,11 @@ public ClientBuilder decorator(DecoratingHttpClientFunction decorator) {
return (ClientBuilder) super.decorator(decorator);
}

@Override
public ClientBuilder clearDecorators() {
return (ClientBuilder) super.clearDecorators();
}

@Override
public ClientBuilder rpcDecorator(
Function<? super RpcClient, ? extends RpcClient> decorator) {
Expand Down
Expand Up @@ -44,12 +44,12 @@ static ClientBuilderParams of(URI uri, Class<?> type, ClientOptions options) {
* Returns a newly created {@link ClientBuilderParams} from the specified properties.
*/
static ClientBuilderParams of(Scheme scheme, EndpointGroup endpointGroup,
@Nullable String path, Class<?> type, ClientOptions options) {
@Nullable String absolutePathRef, Class<?> type, ClientOptions options) {
requireNonNull(scheme, "scheme");
requireNonNull(endpointGroup, "endpointGroup");
requireNonNull(type, "type");
requireNonNull(options, "options");
return new DefaultClientBuilderParams(scheme, endpointGroup, path, type, options);
return new DefaultClientBuilderParams(scheme, endpointGroup, absolutePathRef, type, options);
}

/**
Expand Down
Expand Up @@ -88,11 +88,17 @@ public static ClientDecorationBuilder builder() {
this.rpcDecorators = ImmutableList.copyOf(rpcDecorators);
}

List<Function<? super HttpClient, ? extends HttpClient>> decorators() {
/**
* Returns the HTTP-level decorators.
*/
public List<Function<? super HttpClient, ? extends HttpClient>> decorators() {
return decorators;
}

List<Function<? super RpcClient, ? extends RpcClient>> rpcDecorators() {
/**
* Returns the RPC-level decorators.
*/
public List<Function<? super RpcClient, ? extends RpcClient>> rpcDecorators() {
return rpcDecorators;
}

Expand Down
Expand Up @@ -62,6 +62,15 @@ public ClientDecorationBuilder add(DecoratingHttpClientFunction decorator) {
return add(delegate -> new FunctionalDecoratingHttpClient(delegate, decorator));
}

/**
* Clears all HTTP-level and RPC-level decorators set so far.
*/
public ClientDecorationBuilder clear() {
decorators.clear();
rpcDecorators.clear();
return this;
}

/**
* Adds the specified RPC-level {@code decorator}.
*
Expand Down
Expand Up @@ -125,6 +125,11 @@ public ClientOptionsBuilder decorator(DecoratingHttpClientFunction decorator) {
return (ClientOptionsBuilder) super.decorator(decorator);
}

@Override
public ClientOptionsBuilder clearDecorators() {
return (ClientOptionsBuilder) super.clearDecorators();
}

@Override
public ClientOptionsBuilder rpcDecorator(
Function<? super RpcClient, ? extends RpcClient> decorator) {
Expand Down
Expand Up @@ -163,6 +163,11 @@ public WebClientBuilder decorator(DecoratingHttpClientFunction decorator) {
return (WebClientBuilder) super.decorator(decorator);
}

@Override
public WebClientBuilder clearDecorators() {
return (WebClientBuilder) super.clearDecorators();
}

@Override
public WebClientBuilder addHttpHeader(CharSequence name, Object value) {
return (WebClientBuilder) super.addHttpHeader(name, value);
Expand Down
Expand Up @@ -15,25 +15,18 @@
*/
package com.linecorp.armeria.common.metric;

import static com.google.common.base.MoreObjects.firstNonNull;
import static java.util.Objects.requireNonNull;

import java.util.function.BiFunction;

import com.google.common.collect.ImmutableList;

import com.linecorp.armeria.client.metric.MetricCollectingClient;
import com.linecorp.armeria.common.Flags;
import com.linecorp.armeria.common.HttpMethod;
import com.linecorp.armeria.common.HttpStatus;
import com.linecorp.armeria.common.RequestContext;
import com.linecorp.armeria.common.RequestHeaders;
import com.linecorp.armeria.common.logging.RequestLog;
import com.linecorp.armeria.common.logging.RequestLogProperty;
import com.linecorp.armeria.common.logging.RequestOnlyLog;
import com.linecorp.armeria.internal.common.metric.RequestMetricSupport;
import com.linecorp.armeria.internal.common.metric.DefaultMeterIdPrefixFunction;
import com.linecorp.armeria.server.Route;
import com.linecorp.armeria.server.ServiceRequestContext;
import com.linecorp.armeria.server.VirtualHost;
import com.linecorp.armeria.server.metric.MetricCollectingService;

Expand Down Expand Up @@ -68,49 +61,7 @@ public interface MeterIdPrefixFunction {
* </ul>
*/
static MeterIdPrefixFunction ofDefault(String name) {
requireNonNull(name, "name");
return new MeterIdPrefixFunction() {
@Override
public MeterIdPrefix activeRequestPrefix(MeterRegistry registry, RequestOnlyLog log) {
// hostNamePattern, method, route
final ImmutableList.Builder<Tag> tagListBuilder = ImmutableList.builderWithExpectedSize(3);
buildTags(tagListBuilder, log);
return new MeterIdPrefix(name, tagListBuilder.build());
}

@Override
public MeterIdPrefix completeRequestPrefix(MeterRegistry registry, RequestLog log) {
// hostNamePattern, httpStatus, method, route
final ImmutableList.Builder<Tag> tagListBuilder = ImmutableList.builderWithExpectedSize(4);
buildTags(tagListBuilder, log);
RequestMetricSupport.appendHttpStatusTag(tagListBuilder, log);
return new MeterIdPrefix(name, tagListBuilder.build());
}

/**
* Appends the tags in lexicographical order for better sort performance.
*/
private void buildTags(ImmutableList.Builder<Tag> tagListBuilder, RequestOnlyLog log) {
final RequestContext ctx = log.context();

if (ctx instanceof ServiceRequestContext) {
final ServiceRequestContext sCtx = (ServiceRequestContext) ctx;
tagListBuilder.add(Tag.of(Flags.useLegacyMeterNames() ? "hostnamePattern"
: "hostname.pattern",
sCtx.config().virtualHost().hostnamePattern()));
}

String methodName = log.name();
if (methodName == null) {
final RequestHeaders requestHeaders = log.requestHeaders();
methodName = requestHeaders.method().name();
}
tagListBuilder.add(Tag.of("method", methodName));

final String serviceName = firstNonNull(log.serviceName(), "none");
tagListBuilder.add(Tag.of("service", serviceName));
}
};
return DefaultMeterIdPrefixFunction.of(name);
}

/**
Expand Down
@@ -0,0 +1,122 @@
/*
* Copyright 2020 LINE Corporation
*
* LINE Corporation licenses this file to you 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:
*
* https://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 com.linecorp.armeria.internal.common.metric;

import static com.google.common.base.MoreObjects.firstNonNull;
import static java.util.Objects.requireNonNull;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableList.Builder;

import com.linecorp.armeria.common.Flags;
import com.linecorp.armeria.common.HttpStatus;
import com.linecorp.armeria.common.RequestContext;
import com.linecorp.armeria.common.logging.RequestLog;
import com.linecorp.armeria.common.logging.RequestLogProperty;
import com.linecorp.armeria.common.logging.RequestOnlyLog;
import com.linecorp.armeria.common.metric.MeterIdPrefix;
import com.linecorp.armeria.common.metric.MeterIdPrefixFunction;
import com.linecorp.armeria.server.ServiceRequestContext;

import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;

/**
* Default {@link MeterIdPrefixFunction} implementation.
*/
public final class DefaultMeterIdPrefixFunction implements MeterIdPrefixFunction {

private final String name;

public static MeterIdPrefixFunction of(String name) {
return new DefaultMeterIdPrefixFunction(name);
}

private DefaultMeterIdPrefixFunction(String name) {
this.name = requireNonNull(name, "name");
}

@Override
public MeterIdPrefix activeRequestPrefix(MeterRegistry registry, RequestOnlyLog log) {
/* hostname.pattern, method, service */
final Builder<Tag> tagListBuilder = ImmutableList.builderWithExpectedSize(3);
addActiveRequestPrefixTags(tagListBuilder, log);
return new MeterIdPrefix(name, tagListBuilder.build());
}

@Override
public MeterIdPrefix completeRequestPrefix(MeterRegistry registry, RequestLog log) {
/* hostname.pattern, http.status, method, service */
final Builder<Tag> tagListBuilder = ImmutableList.builderWithExpectedSize(4);
addCompleteRequestPrefixTags(tagListBuilder, log);
return new MeterIdPrefix(name, tagListBuilder.build());
}

/**
* Adds the active request tags in lexicographical order for better sort performance.
* This adds {@code hostname.pattern}, {@code method} and {@code service}, in order.
*/
public static void addActiveRequestPrefixTags(Builder<Tag> tagListBuilder, RequestOnlyLog log) {
requireNonNull(tagListBuilder, "tagListBuilder");
requireNonNull(log, "log");
addHostnamePattern(tagListBuilder, log);
addMethodAndService(tagListBuilder, log);
}

/**
* Adds the complete request tags in lexicographical order for better sort performance.
* This adds {@code hostname.pattern}, {@code http.status}, {@code method} and {@code service}, in order.
*/
public static void addCompleteRequestPrefixTags(Builder<Tag> tagListBuilder, RequestLog log) {
requireNonNull(tagListBuilder, "tagListBuilder");
requireNonNull(log, "log");
addHostnamePattern(tagListBuilder, log);
addHttpStatus(tagListBuilder, log);
addMethodAndService(tagListBuilder, log);
}

/**
* Adds {@code http.status} tag to the {@code tagListBuilder}.
*/
public static void addHttpStatus(Builder<Tag> tagListBuilder, RequestLog log) {
requireNonNull(tagListBuilder, "tagListBuilder");
requireNonNull(log, "log");
// Add the 'httpStatus' tag.
final HttpStatus status;
if (log.isAvailable(RequestLogProperty.RESPONSE_HEADERS)) {
status = log.responseHeaders().status();
} else {
status = HttpStatus.UNKNOWN;
}
tagListBuilder.add(Tag.of(Flags.useLegacyMeterNames() ? "httpStatus" : "http.status",
status.codeAsText()));
}

private static void addHostnamePattern(Builder<Tag> tagListBuilder, RequestOnlyLog log) {
final RequestContext ctx = log.context();
if (ctx instanceof ServiceRequestContext) {
final ServiceRequestContext sCtx = (ServiceRequestContext) ctx;
tagListBuilder.add(Tag.of(Flags.useLegacyMeterNames() ? "hostnamePattern"
: "hostname.pattern",
sCtx.config().virtualHost().hostnamePattern()));
}
}

private static void addMethodAndService(Builder<Tag> tagListBuilder, RequestOnlyLog log) {
tagListBuilder.add(Tag.of("method", log.name()));
tagListBuilder.add(Tag.of("service", firstNonNull(log.serviceName(), "none")));
}
}
Expand Up @@ -18,20 +18,16 @@

import static com.linecorp.armeria.common.metric.MoreMeters.newDistributionSummary;
import static com.linecorp.armeria.common.metric.MoreMeters.newTimer;
import static java.util.Objects.requireNonNull;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.concurrent.atomic.LongAdder;

import javax.annotation.Nullable;

import com.google.common.collect.ImmutableList;

import com.linecorp.armeria.client.ResponseTimeoutException;
import com.linecorp.armeria.client.WriteTimeoutException;
import com.linecorp.armeria.common.Flags;
import com.linecorp.armeria.common.HttpStatus;
import com.linecorp.armeria.common.RequestContext;
import com.linecorp.armeria.common.RpcResponse;
import com.linecorp.armeria.common.logging.ClientConnectionTimings;
Expand All @@ -44,7 +40,6 @@
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.DistributionSummary;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.Timer;
import io.netty.util.AttributeKey;

Expand Down Expand Up @@ -73,23 +68,6 @@ public static void setup(RequestContext ctx, AttributeKey<Boolean> requestMetric
.thenAccept(log -> onRequest(log, meterIdPrefixFunction, server));
}

/**
* Appends {@link HttpStatus} to {@link Tag}.
*/
public static void appendHttpStatusTag(ImmutableList.Builder<Tag> tagListBuilder, RequestLog log) {
requireNonNull(tagListBuilder, "tagListBuilder");
requireNonNull(log, "log");
// Add the 'httpStatus' tag.
final HttpStatus status;
if (log.isAvailable(RequestLogProperty.RESPONSE_HEADERS)) {
status = log.responseHeaders().status();
} else {
status = HttpStatus.UNKNOWN;
}
tagListBuilder.add(Tag.of(Flags.useLegacyMeterNames() ? "httpStatus" : "http.status",
status.codeAsText()));
}

private static void onRequest(RequestLog log, MeterIdPrefixFunction meterIdPrefixFunction, boolean server) {
final RequestContext ctx = log.context();
final MeterRegistry registry = ctx.meterRegistry();
Expand Down
Expand Up @@ -93,7 +93,7 @@ void testDecorators() {
b.option(ClientOptions.DECORATION.newValue(ClientDecoration.builder()
.add(decorator2)
.build()));
assertThat(b.build().decoration().decorators()).containsExactly(decorator, decorator2);
assertThat(b.build().decoration().decorators()).containsSequence(decorator, decorator2);

// Add an RPC decorator.
final Function<? super RpcClient, ? extends RpcClient> rpcDecorator =
Expand All @@ -102,8 +102,21 @@ void testDecorators() {
.addRpc(rpcDecorator)
.build()));

assertThat(b.build().decoration().decorators()).containsExactly(decorator, decorator2);
assertThat(b.build().decoration().decorators()).containsSequence(decorator, decorator2);
assertThat(b.build().decoration().rpcDecorators()).containsExactly(rpcDecorator);

final Function<? super HttpClient, ? extends HttpClient> decorator3 =
DecodingClient.newDecorator();
// Insert decorator at first.
final ClientDecoration decoration = b.build().decoration();
b.clearDecorators();
assertThat(b.build().decoration().decorators()).isEmpty();
assertThat(b.build().decoration().rpcDecorators()).isEmpty();

b.decorator(decorator3);
decoration.decorators().forEach(b::decorator);
assertThat(b.build().decoration().decorators()).containsSequence(decorator3, decorator, decorator2);
assertThat(b.build().decoration().rpcDecorators()).isEmpty();
}

@Test
Expand Down

0 comments on commit 1929ba0

Please sign in to comment.