io.temporal.serviceclient.GrpcMetricsInterceptor Maven / Gradle / Ivy
/*
* Copyright (C) 2022 Temporal Technologies, Inc. All Rights Reserved.
*
* Copyright (C) 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Modifications copyright (C) 2017 Uber Technologies, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this material 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.temporal.serviceclient;
import static io.temporal.serviceclient.MetricsTag.OPERATION_NAME;
import static io.temporal.serviceclient.MetricsTag.STATUS_CODE;
import com.uber.m3.tally.Scope;
import com.uber.m3.tally.Stopwatch;
import com.uber.m3.util.ImmutableMap;
import io.grpc.CallOptions;
import io.grpc.Channel;
import io.grpc.ClientCall;
import io.grpc.ClientInterceptor;
import io.grpc.ForwardingClientCall;
import io.grpc.ForwardingClientCallListener;
import io.grpc.Metadata;
import io.grpc.MethodDescriptor;
import io.grpc.ServiceDescriptor;
import io.grpc.Status;
import io.temporal.api.workflowservice.v1.WorkflowServiceGrpc;
import java.util.*;
/** Reports metrics on GRPC service calls */
class GrpcMetricsInterceptor implements ClientInterceptor {
private static final Map> STATUS_CODE_TAGS;
private final Scope defaultScope;
private final Map, Map> methodTags;
GrpcMetricsInterceptor(Scope scope) {
this.defaultScope = scope.tagged(MetricsTag.defaultTags(MetricsTag.DEFAULT_VALUE));
ServiceDescriptor descriptor = WorkflowServiceGrpc.getServiceDescriptor();
String serviceName = descriptor.getName();
Map, Map> methodTags = new HashMap<>();
Collection> methods = descriptor.getMethods();
for (MethodDescriptor, ?> method : methods) {
int beginIndex = serviceName.length() + 1;
String name = method.getFullMethodName().substring(beginIndex);
Map tags =
new ImmutableMap.Builder(1).put(OPERATION_NAME, name).build();
methodTags.put(method, tags);
}
this.methodTags = Collections.unmodifiableMap(methodTags);
}
@Override
public ClientCall interceptCall(
MethodDescriptor method, CallOptions callOptions, Channel next) {
Scope scope = callOptions.getOption(MetricsTag.METRICS_TAGS_CALL_OPTIONS_KEY);
if (scope == null) {
scope = defaultScope;
}
Map tags = methodTags.get(method);
scope = scope.tagged(tags);
return new MetricsClientCall<>(next, method, callOptions, scope);
}
private static class MetricsClientCall
extends ForwardingClientCall.SimpleForwardingClientCall {
private final Scope metricsScope;
private final Stopwatch sw;
private final boolean longPoll;
MetricsClientCall(
Channel next,
MethodDescriptor method,
CallOptions callOptions,
Scope metricsScope) {
super(next.newCall(method, callOptions));
this.metricsScope = metricsScope;
longPoll = LongPollUtil.isLongPoll(method, callOptions);
if (longPoll) {
metricsScope.counter(MetricsType.TEMPORAL_LONG_REQUEST).inc(1);
sw = metricsScope.timer(MetricsType.TEMPORAL_LONG_REQUEST_LATENCY).start();
} else {
metricsScope.counter(MetricsType.TEMPORAL_REQUEST).inc(1);
sw = metricsScope.timer(MetricsType.TEMPORAL_REQUEST_LATENCY).start();
}
}
@Override
public void sendMessage(ReqT message) {
super.sendMessage(message);
}
@Override
public void start(Listener responseListener, Metadata headers) {
Listener listener =
new ForwardingClientCallListener.SimpleForwardingClientCallListener(
responseListener) {
@Override
public void onClose(Status status, Metadata trailers) {
try {
// This method SHOULDN'T throw according to it's javadoc,
// but we allow users setting custom interceptors.
// So we protect from an incorrectly implemented custom user gRPC interceptor
// by putting temporal metrics code into finally block
super.onClose(status, trailers);
} finally {
sw.stop();
if (!status.isOk()) {
Scope scope = metricsScope.tagged(STATUS_CODE_TAGS.get(status.getCode()));
if (longPoll) {
scope.counter(MetricsType.TEMPORAL_LONG_REQUEST_FAILURE).inc(1);
} else {
scope.counter(MetricsType.TEMPORAL_REQUEST_FAILURE).inc(1);
}
}
}
}
};
super.start(listener, headers);
}
}
static {
Map> codeTags = new EnumMap<>(Status.Code.class);
Arrays.stream(Status.Code.values())
.forEach(
code ->
codeTags.put(
code,
new ImmutableMap.Builder(1)
.put(STATUS_CODE, code.name())
.build()));
STATUS_CODE_TAGS = Collections.unmodifiableMap(codeTags);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy