mirror of https://github.com/grpc/grpc-java.git
services: implement a lb policy agnostic call metric recorder for backend applications (#6004)
* services: implement a lb policy agnostic call metric recorder for backend applications. * Renamed snapshot() to finalizeAndDump() and make it package-private with an internal accessor class. * Added Javadoc link for Context. * Added ExperimentalApi annotation. * Added since annotations.
This commit is contained in:
parent
06e9b88147
commit
f15a6bd363
|
|
@ -0,0 +1,106 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 The gRPC Authors
|
||||||
|
*
|
||||||
|
* 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.grpc.services;
|
||||||
|
|
||||||
|
import io.grpc.Context;
|
||||||
|
import io.grpc.ExperimentalApi;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import javax.annotation.concurrent.ThreadSafe;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility to record call metrics for load-balancing. One instance per call.
|
||||||
|
*/
|
||||||
|
@ExperimentalApi("https://github.com/grpc/grpc-java/issues/6012")
|
||||||
|
@ThreadSafe
|
||||||
|
public final class CallMetricRecorder {
|
||||||
|
private static final CallMetricRecorder NOOP = new CallMetricRecorder().disable();
|
||||||
|
|
||||||
|
static final Context.Key<CallMetricRecorder> CONTEXT_KEY =
|
||||||
|
Context.key("io.grpc.services.CallMetricRecorder");
|
||||||
|
|
||||||
|
private final AtomicReference<ConcurrentHashMap<String, Double>> metrics =
|
||||||
|
new AtomicReference<>();
|
||||||
|
private volatile boolean disabled;
|
||||||
|
|
||||||
|
CallMetricRecorder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the call metric recorder attached to the current {@link Context}. If there is none,
|
||||||
|
* returns a no-op recorder.
|
||||||
|
*
|
||||||
|
* <p><strong>IMPORTANT:</strong>It returns the recorder specifically for the current RPC call.
|
||||||
|
* <b>DO NOT</b> save the returned object or share it between different RPC calls.
|
||||||
|
*
|
||||||
|
* <p><strong>IMPORTANT:</strong>It must be called under the {@link Context} under which the RPC
|
||||||
|
* handler was called. If it is called from a different thread, the Context must be propagated to
|
||||||
|
* the same thread, e.g., with {@link Context#wrap(Runnable)}.
|
||||||
|
*
|
||||||
|
* @since 1.23.0
|
||||||
|
*/
|
||||||
|
public static CallMetricRecorder getCurrent() {
|
||||||
|
CallMetricRecorder recorder = CONTEXT_KEY.get();
|
||||||
|
return recorder != null ? recorder : NOOP;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Records a call metric measurement. If RPC has already finished, this method is no-op.
|
||||||
|
*
|
||||||
|
* <p>A latter record will overwrite its former name-sakes.
|
||||||
|
*
|
||||||
|
* @return this recorder object
|
||||||
|
* @since 1.23.0
|
||||||
|
*/
|
||||||
|
public CallMetricRecorder recordCallMetric(String name, double value) {
|
||||||
|
if (disabled) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
if (metrics.get() == null) {
|
||||||
|
// The chance of race of creation of the map should be very small, so it should be fine
|
||||||
|
// to create these maps that might be discarded.
|
||||||
|
metrics.compareAndSet(null, new ConcurrentHashMap<String, Double>());
|
||||||
|
}
|
||||||
|
metrics.get().put(name, value);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns all save metric values. No more metric values will be recorded after this method is
|
||||||
|
* called. Calling this method multiple times returns the same collection of metric values.
|
||||||
|
*
|
||||||
|
* @return a map containing all saved metric name-value pairs.
|
||||||
|
*/
|
||||||
|
Map<String, Double> finalizeAndDump() {
|
||||||
|
disabled = true;
|
||||||
|
Map<String, Double> savedMetrics = metrics.get();
|
||||||
|
if (savedMetrics == null) {
|
||||||
|
return Collections.emptyMap();
|
||||||
|
}
|
||||||
|
return Collections.unmodifiableMap(savedMetrics);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Turn this recorder into a no-op one.
|
||||||
|
*/
|
||||||
|
private CallMetricRecorder disable() {
|
||||||
|
disabled = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 The gRPC Authors
|
||||||
|
*
|
||||||
|
* 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.grpc.services;
|
||||||
|
|
||||||
|
import io.grpc.Internal;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internal {@link CallMetricRecorder} accessor. This is intended for usage internal to the gRPC
|
||||||
|
* team. If you *really* think you need to use this, contact the gRPC team first.
|
||||||
|
*/
|
||||||
|
@Internal
|
||||||
|
public final class InternalCallMetricRecorder {
|
||||||
|
|
||||||
|
// Prevent instantiation.
|
||||||
|
private InternalCallMetricRecorder() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Map<String, Double> finalizeAndDump(CallMetricRecorder recorder) {
|
||||||
|
return recorder.finalizeAndDump();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2019 The gRPC Authors
|
||||||
|
*
|
||||||
|
* 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.grpc.services;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.JUnit4;
|
||||||
|
|
||||||
|
/** Tests for {@link BinlogHelper}. */
|
||||||
|
@RunWith(JUnit4.class)
|
||||||
|
public class CallMetricRecorderTest {
|
||||||
|
|
||||||
|
private final CallMetricRecorder recorder = new CallMetricRecorder();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void dumpGivesEmptyResultWhenNoSavedMetricValues() {
|
||||||
|
assertThat(recorder.finalizeAndDump()).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void dumpDumpsAllSavedMetricValues() {
|
||||||
|
recorder.recordCallMetric("ssd", 154353.423);
|
||||||
|
recorder.recordCallMetric("cpu", 0.1367);
|
||||||
|
recorder.recordCallMetric("mem", 1437.34);
|
||||||
|
|
||||||
|
Map<String, Double> dump = recorder.finalizeAndDump();
|
||||||
|
assertThat(dump)
|
||||||
|
.containsExactly("ssd", 154353.423, "cpu", 0.1367, "mem", 1437.34);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void noMetricsRecordedAfterSnapshot() {
|
||||||
|
Map<String, Double> initDump = recorder.finalizeAndDump();
|
||||||
|
recorder.recordCallMetric("cpu", 154353.423);
|
||||||
|
assertThat(recorder.finalizeAndDump()).isEqualTo(initDump);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue