timer) {
+ this.clientCallDuration = timer;
+ return this;
+ }
+
+ public MetricsClientMeters build() {
+ return new MetricsClientMeters(this);
+ }
+ }
+}
diff --git a/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/metrics/MetricsClientStreamTracers.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/metrics/MetricsClientStreamTracers.java
new file mode 100644
index 000000000..efc22f6c0
--- /dev/null
+++ b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/metrics/MetricsClientStreamTracers.java
@@ -0,0 +1,270 @@
+/*
+ * Copyright (c) 2016-2023 The gRPC-Spring 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 net.devh.boot.grpc.client.metrics;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicLongFieldUpdater;
+import java.util.function.Supplier;
+
+import javax.annotation.concurrent.GuardedBy;
+
+import com.google.common.base.Stopwatch;
+
+import io.grpc.ClientStreamTracer;
+import io.grpc.ClientStreamTracer.StreamInfo;
+import io.grpc.Deadline;
+import io.grpc.Metadata;
+import io.grpc.Status;
+import io.grpc.Status.Code;
+import io.micrometer.core.instrument.Tags;
+import net.devh.boot.grpc.common.util.Constants;
+
+/**
+ * Provides factories for {@link io.grpc.StreamTracer} that records metrics.
+ *
+ *
+ * On the client-side, a factory is created for each call, and the factory creates a stream tracer for each attempt.
+ *
+ * Note: This class uses experimental grpc-java-API features.
+ */
+final class MetricsClientStreamTracers {
+ private static final Supplier STOPWATCH_SUPPLIER = Stopwatch::createUnstarted;
+ private final Supplier stopwatchSupplier;
+ private static final String INSTRUMENTATION_SOURCE_TAG_KEY = "instrumentation_source";
+ private static final String INSTRUMENTATION_VERSION_TAG_KEY = "instrumentation_version";
+
+ MetricsClientStreamTracers() {
+ this(STOPWATCH_SUPPLIER);
+ }
+
+ MetricsClientStreamTracers(Supplier stopwatchSupplier) {
+ this.stopwatchSupplier = checkNotNull(stopwatchSupplier, "stopwatchSupplier");
+ }
+
+ private static final class ClientTracer extends ClientStreamTracer {
+ private final MetricsClientStreamTracers tracerModule;
+ private final CallAttemptsTracerFactory attemptsState;
+ private final MetricsClientMeters metricsClientMeters;
+ private static final AtomicLongFieldUpdater outboundWireSizeUpdater =
+ AtomicLongFieldUpdater.newUpdater(ClientTracer.class, "outboundWireSize");
+ private static final AtomicLongFieldUpdater inboundWireSizeUpdater =
+ AtomicLongFieldUpdater.newUpdater(ClientTracer.class, "inboundWireSize");
+ private volatile long outboundWireSize;
+ private volatile long inboundWireSize;
+ private final StreamInfo info;
+ private final String fullMethodName;
+ final AtomicBoolean inboundReceivedOrClosed = new AtomicBoolean();
+ final Stopwatch stopwatch;
+ Code statusCode;
+ long attemptNanos;
+
+ ClientTracer(CallAttemptsTracerFactory attemptsState, MetricsClientStreamTracers tracers,
+ StreamInfo info,
+ String fullMethodName, MetricsClientMeters metricsClientMeters) {
+ this.attemptsState = attemptsState;
+ this.tracerModule = tracers;
+ this.info = info;
+ this.fullMethodName = fullMethodName;
+ this.metricsClientMeters = metricsClientMeters;
+ this.stopwatch = tracers.stopwatchSupplier.get().start();
+ }
+
+ @Override
+ public void outboundWireSize(long bytes) {
+ outboundWireSizeUpdater.getAndAdd(this, bytes);
+ }
+
+ @Override
+ public void inboundWireSize(long bytes) {
+ inboundWireSizeUpdater.getAndAdd(this, bytes);
+ }
+
+ public void inboundMessage(int seqNo) {
+ if (inboundReceivedOrClosed.compareAndSet(false, true)) {
+ // Because inboundUncompressedSize() might be called after streamClosed(),
+ // we will report stats in callEnded(). Note that this attempt is already committed.
+ attemptsState.inboundMetricTracer = this;
+ }
+ }
+
+ @Override
+ public void streamClosed(Status status) {
+ stopwatch.stop();
+ attemptNanos = stopwatch.elapsed(TimeUnit.NANOSECONDS);
+ Deadline deadline = info.getCallOptions().getDeadline();
+ statusCode = status.getCode();
+ if (statusCode == Status.Code.CANCELLED && deadline != null) {
+ // When the server's deadline expires, it can only reset the stream with CANCEL and no
+ // description. Since our timer may be delayed in firing, we double-check the deadline and
+ // turn the failure into the likely more helpful DEADLINE_EXCEEDED status.
+ if (deadline.isExpired()) {
+ statusCode = Code.DEADLINE_EXCEEDED;
+ }
+ }
+ attemptsState.attemptEnded();
+ if (inboundReceivedOrClosed.compareAndSet(false, true)) {
+ // Stream is closed early. So no need to record metrics for any inbound events after this
+ // point.
+ recordFinishedAttempt();
+ } // Otherwise will report metrics in callEnded() to guarantee all inbound metrics are
+ // recorded.
+ }
+
+ void recordFinishedAttempt() {
+ Tags attemptMetricTags =
+ Tags.of("grpc.method", fullMethodName,
+ "grpc.status", statusCode.toString(),
+ INSTRUMENTATION_SOURCE_TAG_KEY, Constants.LIBRARY_NAME,
+ INSTRUMENTATION_VERSION_TAG_KEY, Constants.VERSION);
+ this.metricsClientMeters.getClientAttemptDuration()
+ .withTags(attemptMetricTags)
+ .record(attemptNanos, TimeUnit.NANOSECONDS);
+ this.metricsClientMeters.getSentMessageSizeDistribution()
+ .withTags(attemptMetricTags)
+ .record(outboundWireSize);
+ this.metricsClientMeters.getReceivedMessageSizeDistribution()
+ .withTags(attemptMetricTags)
+ .record(inboundWireSize);
+ }
+ }
+
+ static final class CallAttemptsTracerFactory extends ClientStreamTracer.Factory {
+ ClientTracer inboundMetricTracer;
+ private final MetricsClientStreamTracers tracerModule;
+ private final MetricsClientMeters metricsClientMeters;
+ private final Stopwatch attemptStopwatch;
+ private final Stopwatch clientCallStopWatch;
+ private final String fullMethodName;
+ private final Object lock = new Object();
+ private final AtomicLong attemptsPerCall = new AtomicLong();
+ private long callLatencyNanos;
+ private Status status;
+ @GuardedBy("lock")
+ private boolean callEnded;
+ @GuardedBy("lock")
+ private int activeStreams;
+ @GuardedBy("lock")
+ private boolean finishedCallToBeRecorded;
+
+ CallAttemptsTracerFactory(MetricsClientStreamTracers tracerModule, String fullMethodName,
+ MetricsClientMeters metricsClientMeters) {
+ this.tracerModule = checkNotNull(tracerModule, "tracerModule");
+ this.fullMethodName = checkNotNull(fullMethodName, "fullMethodName");
+ this.metricsClientMeters = checkNotNull(metricsClientMeters, "metricsMeters");
+ this.attemptStopwatch = tracerModule.stopwatchSupplier.get();
+ this.clientCallStopWatch = tracerModule.stopwatchSupplier.get().start();
+
+ // Record here in case newClientStreamTracer() would never be called.
+ this.metricsClientMeters.getAttemptCounter()
+ .withTags(Tags.of("grpc.method", fullMethodName,
+ INSTRUMENTATION_SOURCE_TAG_KEY, Constants.LIBRARY_NAME,
+ INSTRUMENTATION_VERSION_TAG_KEY, Constants.VERSION))
+ .increment();
+ }
+
+ @Override
+ public ClientStreamTracer newClientStreamTracer(StreamInfo info, Metadata metadata) {
+ synchronized (lock) {
+ if (finishedCallToBeRecorded) {
+ // This can be the case when the called is cancelled but a retry attempt is created.
+ return new ClientStreamTracer() {};
+ }
+ if (++activeStreams == 1 && attemptStopwatch.isRunning()) {
+ attemptStopwatch.stop();
+ }
+ }
+ // Skip recording for the first time, since it is already recorded in
+ // CallAttemptsTracerFactory constructor. attemptsPerCall will be non-zero after the first
+ // attempt, as first attempt cannot be a transparent retry.
+ if (attemptsPerCall.get() > 0) {
+ this.metricsClientMeters.getAttemptCounter()
+ .withTags((Tags.of("grpc.method", fullMethodName,
+ INSTRUMENTATION_SOURCE_TAG_KEY, Constants.LIBRARY_NAME,
+ INSTRUMENTATION_VERSION_TAG_KEY, Constants.VERSION)))
+ .increment();
+ }
+ if (!info.isTransparentRetry()) {
+ attemptsPerCall.incrementAndGet();
+ }
+
+ return new ClientTracer(this, tracerModule, info, fullMethodName, metricsClientMeters);
+ }
+
+ // Called when each attempt is ended
+ void attemptEnded() {
+ boolean shouldRecordFinishedCall = false;
+ synchronized (lock) {
+ if (--activeStreams == 0) {
+ attemptStopwatch.start();
+ if (callEnded && !finishedCallToBeRecorded) {
+ shouldRecordFinishedCall = true;
+ finishedCallToBeRecorded = true;
+ }
+ }
+ }
+ if (shouldRecordFinishedCall) {
+ recordFinishedCall();
+ }
+ }
+
+ void callEnded(Status status) {
+ clientCallStopWatch.stop();
+ this.status = status;
+ boolean shouldRecordFinishedCall = false;
+ synchronized (lock) {
+ if (callEnded) {
+ return;
+ }
+ callEnded = true;
+ if (activeStreams == 0 && !finishedCallToBeRecorded) {
+ shouldRecordFinishedCall = true;
+ finishedCallToBeRecorded = true;
+ }
+ }
+ if (shouldRecordFinishedCall) {
+ recordFinishedCall();
+ }
+ }
+
+ void recordFinishedCall() {
+ if (attemptsPerCall.get() == 0) {
+ ClientTracer tracer = new ClientTracer(this, tracerModule, null, fullMethodName,
+ metricsClientMeters);
+ tracer.attemptNanos = attemptStopwatch.elapsed(TimeUnit.NANOSECONDS);
+ tracer.statusCode = status.getCode();
+ tracer.recordFinishedAttempt();
+ } else if (inboundMetricTracer != null) {
+ // activeStreams has been decremented to 0 by attemptEnded(),
+ // so inboundMetricTracer.statusCode is guaranteed to be assigned already.
+ inboundMetricTracer.recordFinishedAttempt();
+ }
+ callLatencyNanos = clientCallStopWatch.elapsed(TimeUnit.NANOSECONDS);
+ Tags clientCallMetricTags =
+ Tags.of("grpc.method", this.fullMethodName,
+ "grpc.status", status.getCode().toString(),
+ INSTRUMENTATION_SOURCE_TAG_KEY, Constants.LIBRARY_NAME,
+ INSTRUMENTATION_VERSION_TAG_KEY, Constants.VERSION);
+ this.metricsClientMeters.getClientCallDuration()
+ .withTags(clientCallMetricTags)
+ .record(callLatencyNanos, TimeUnit.NANOSECONDS);
+ }
+ }
+}
diff --git a/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/metrics/package-info.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/metrics/package-info.java
new file mode 100644
index 000000000..6f6782a25
--- /dev/null
+++ b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/metrics/package-info.java
@@ -0,0 +1,5 @@
+/**
+ * A package containing client side classes for grpc metric collection.
+ */
+
+package net.devh.boot.grpc.client.metrics;
diff --git a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/nameresolver/DiscoveryClientNameResolver.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/nameresolver/DiscoveryClientNameResolver.java
similarity index 84%
rename from grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/nameresolver/DiscoveryClientNameResolver.java
rename to grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/nameresolver/DiscoveryClientNameResolver.java
index b3e107eb7..d12f92c69 100644
--- a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/nameresolver/DiscoveryClientNameResolver.java
+++ b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/nameresolver/DiscoveryClientNameResolver.java
@@ -22,6 +22,7 @@
import static net.devh.boot.grpc.client.nameresolver.DiscoveryClientResolverFactory.DISCOVERY_INSTANCE_ID_KEY;
import static net.devh.boot.grpc.client.nameresolver.DiscoveryClientResolverFactory.DISCOVERY_SERVICE_NAME_KEY;
import static net.devh.boot.grpc.common.util.GrpcUtils.CLOUD_DISCOVERY_METADATA_PORT;
+import static net.devh.boot.grpc.common.util.GrpcUtils.CLOUD_DISCOVERY_METADATA_SERVICE_CONFIG;
import java.net.InetSocketAddress;
import java.util.List;
@@ -35,6 +36,8 @@
import org.springframework.util.CollectionUtils;
import com.google.common.collect.Lists;
+import com.google.gson.Gson;
+import com.google.gson.JsonSyntaxException;
import io.grpc.Attributes;
import io.grpc.Attributes.Builder;
@@ -58,6 +61,7 @@ public class DiscoveryClientNameResolver extends NameResolver {
@Deprecated
private static final String LEGACY_CLOUD_DISCOVERY_METADATA_PORT = "gRPC.port";
private static final List KEEP_PREVIOUS = null;
+ private static final Gson GSON = new Gson();
private final String name;
private final DiscoveryClient client;
@@ -65,6 +69,7 @@ public class DiscoveryClientNameResolver extends NameResolver {
private final Consumer shutdownHook;
private final SharedResourceHolder.Resource executorResource;
private final boolean usingExecutorResource;
+ private final ServiceConfigParser serviceConfigParser;
// The field must be accessed from syncContext, although the methods on an Listener2 can be called
// from any thread.
@@ -93,6 +98,7 @@ public DiscoveryClientNameResolver(final String name, final DiscoveryClient clie
this.executor = args.getOffloadExecutor();
this.usingExecutorResource = this.executor == null;
this.executorResource = executorResource;
+ this.serviceConfigParser = args.getServiceConfigParser();
}
/**
@@ -187,6 +193,55 @@ protected int getGrpcPort(final ServiceInstance instance) {
}
}
+ /**
+ * Extracts and parse gRPC service config from the given service instances.
+ *
+ * @param instances The list of instances to extract the service config from.
+ * @return Parsed gRPC service config or null.
+ */
+ private ConfigOrError resolveServiceConfig(List instances) {
+ final String serviceConfig = getServiceConfig(instances);
+ if (serviceConfig == null) {
+ return null;
+ }
+ log.debug("Found service config for {}", getName());
+ if (log.isTraceEnabled()) {
+ // This is to avoid blowing log into several lines if newlines present in service config string.
+ final String logStr = serviceConfig.replace("\r", "\\r").replace("\n", "\\n");
+ log.trace("Service config for {}: {}", getName(), logStr);
+ }
+ try {
+ @SuppressWarnings("unchecked")
+ Map parsedServiceConfig = GSON.fromJson(serviceConfig, Map.class);
+ return serviceConfigParser.parseServiceConfig(parsedServiceConfig);
+ } catch (JsonSyntaxException e) {
+ return ConfigOrError.fromError(
+ Status.UNKNOWN
+ .withDescription("Failed to parse grpc service config")
+ .withCause(e));
+ }
+ }
+
+ /**
+ * Extracts the gRPC service config string from the given service instances.
+ *
+ * @param instances The list of instances to extract the service config from.
+ * @return The gRPC service config or null.
+ */
+ protected String getServiceConfig(final List instances) {
+ for (final ServiceInstance inst : instances) {
+ final Map metadata = inst.getMetadata();
+ if (metadata == null || metadata.isEmpty()) {
+ continue;
+ }
+ final String metaValue = metadata.get(CLOUD_DISCOVERY_METADATA_SERVICE_CONFIG);
+ if (metaValue != null && !metaValue.isEmpty()) {
+ return metaValue;
+ }
+ }
+ return null;
+ }
+
/**
* Gets the attributes from the service instance for later use in a load balancer. Can be overwritten to convert
* custom attributes.
@@ -318,6 +373,7 @@ private List resolveInternal() {
log.debug("Ready to update server list for {}", getName());
this.savedListener.onResult(ResolutionResult.newBuilder()
.setAddresses(toTargets(newInstanceList))
+ .setServiceConfig(resolveServiceConfig(newInstanceList))
.build());
log.info("Done updating server list for {}", getName());
return newInstanceList;
diff --git a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/nameresolver/DiscoveryClientResolverFactory.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/nameresolver/DiscoveryClientResolverFactory.java
similarity index 100%
rename from grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/nameresolver/DiscoveryClientResolverFactory.java
rename to grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/nameresolver/DiscoveryClientResolverFactory.java
diff --git a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/nameresolver/NameResolverRegistration.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/nameresolver/NameResolverRegistration.java
similarity index 100%
rename from grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/nameresolver/NameResolverRegistration.java
rename to grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/nameresolver/NameResolverRegistration.java
diff --git a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/nameresolver/StaticNameResolver.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/nameresolver/StaticNameResolver.java
similarity index 100%
rename from grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/nameresolver/StaticNameResolver.java
rename to grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/nameresolver/StaticNameResolver.java
diff --git a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/nameresolver/StaticNameResolverProvider.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/nameresolver/StaticNameResolverProvider.java
similarity index 100%
rename from grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/nameresolver/StaticNameResolverProvider.java
rename to grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/nameresolver/StaticNameResolverProvider.java
diff --git a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/nameresolver/package-info.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/nameresolver/package-info.java
similarity index 100%
rename from grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/nameresolver/package-info.java
rename to grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/nameresolver/package-info.java
diff --git a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/security/CallCredentialsHelper.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/security/CallCredentialsHelper.java
similarity index 100%
rename from grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/security/CallCredentialsHelper.java
rename to grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/security/CallCredentialsHelper.java
diff --git a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/security/package-info.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/security/package-info.java
similarity index 100%
rename from grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/security/package-info.java
rename to grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/security/package-info.java
diff --git a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/stubfactory/AsyncStubFactory.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/stubfactory/AsyncStubFactory.java
similarity index 100%
rename from grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/stubfactory/AsyncStubFactory.java
rename to grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/stubfactory/AsyncStubFactory.java
diff --git a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/stubfactory/BlockingStubFactory.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/stubfactory/BlockingStubFactory.java
similarity index 100%
rename from grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/stubfactory/BlockingStubFactory.java
rename to grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/stubfactory/BlockingStubFactory.java
diff --git a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/stubfactory/FallbackStubFactory.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/stubfactory/FallbackStubFactory.java
similarity index 100%
rename from grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/stubfactory/FallbackStubFactory.java
rename to grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/stubfactory/FallbackStubFactory.java
diff --git a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/stubfactory/FutureStubFactory.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/stubfactory/FutureStubFactory.java
similarity index 100%
rename from grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/stubfactory/FutureStubFactory.java
rename to grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/stubfactory/FutureStubFactory.java
diff --git a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/stubfactory/StandardJavaGrpcStubFactory.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/stubfactory/StandardJavaGrpcStubFactory.java
similarity index 100%
rename from grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/stubfactory/StandardJavaGrpcStubFactory.java
rename to grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/stubfactory/StandardJavaGrpcStubFactory.java
diff --git a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/stubfactory/StubFactory.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/stubfactory/StubFactory.java
similarity index 100%
rename from grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/stubfactory/StubFactory.java
rename to grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/stubfactory/StubFactory.java
diff --git a/grpc-client-spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/grpc-client-spring-boot-starter/src/main/resources/META-INF/additional-spring-configuration-metadata.json
similarity index 95%
rename from grpc-client-spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json
rename to grpc-client-spring-boot-starter/src/main/resources/META-INF/additional-spring-configuration-metadata.json
index c58150d25..44903c96e 100644
--- a/grpc-client-spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json
+++ b/grpc-client-spring-boot-starter/src/main/resources/META-INF/additional-spring-configuration-metadata.json
@@ -53,13 +53,6 @@
"description": "Whether keepAlive should be enabled.",
"defaultValue": false
},
- {
- "name": "grpc.client.GLOBAL.full-stream-decompression",
- "type": "java.lang.Boolean",
- "sourceType": "net.devh.boot.grpc.client.config.GrpcChannelProperties",
- "description": "Whether full-stream decompression of inbound streams should be enabled.",
- "defaultValue": false
- },
{
"name": "grpc.client.GLOBAL.keep-alive-time",
"type": "java.time.Duration",
@@ -94,6 +87,12 @@
"sourceType": "net.devh.boot.grpc.client.config.GrpcChannelProperties",
"description": "The maximum message size allowed to be received by the channel.\nIf not set (null) then it will default to gRPC's default.\nIf set to -1 then it will use the highest possible limit (not recommended)."
},
+ {
+ "name": "grpc.client.GLOBAL.max-inbound-metadata-size",
+ "type": "org.springframework.util.unit.DataSize",
+ "sourceType": "net.devh.boot.grpc.client.config.GrpcChannelProperties",
+ "description": "the maximum size of metadata in bytes allowed to be received. \nIf not set (null) then it will default to gRPC's default. \nIf set to {@code -1} then it will use the highest possible limit (not recommended)."
+ },
{
"name": "grpc.client.GLOBAL.negotiation-type",
"type": "net.devh.boot.grpc.client.config.NegotiationType",
@@ -158,4 +157,4 @@
"description": "The path to the trusted certificate collection.\nIf not set (null) it will use the system's default collection (Default).\nThis collection will be used to verify server certificates."
}
]
-}
\ No newline at end of file
+}
diff --git a/grpc-client-spring-boot-autoconfigure/src/main/resources/META-INF/services/io.grpc.NameResolverProvider b/grpc-client-spring-boot-starter/src/main/resources/META-INF/services/io.grpc.NameResolverProvider
similarity index 100%
rename from grpc-client-spring-boot-autoconfigure/src/main/resources/META-INF/services/io.grpc.NameResolverProvider
rename to grpc-client-spring-boot-starter/src/main/resources/META-INF/services/io.grpc.NameResolverProvider
diff --git a/grpc-client-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/grpc-client-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
similarity index 100%
rename from grpc-client-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
rename to grpc-client-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
diff --git a/grpc-client-spring-boot-autoconfigure/src/test/java/net/devh/boot/grpc/client/channelfactory/InProcessChannelFactoryTest.java b/grpc-client-spring-boot-starter/src/test/java/net/devh/boot/grpc/client/channelfactory/InProcessChannelFactoryTest.java
similarity index 100%
rename from grpc-client-spring-boot-autoconfigure/src/test/java/net/devh/boot/grpc/client/channelfactory/InProcessChannelFactoryTest.java
rename to grpc-client-spring-boot-starter/src/test/java/net/devh/boot/grpc/client/channelfactory/InProcessChannelFactoryTest.java
diff --git a/grpc-client-spring-boot-autoconfigure/src/test/java/net/devh/boot/grpc/client/config/GrpcChannelPropertiesConfig.java b/grpc-client-spring-boot-starter/src/test/java/net/devh/boot/grpc/client/config/GrpcChannelPropertiesConfig.java
similarity index 100%
rename from grpc-client-spring-boot-autoconfigure/src/test/java/net/devh/boot/grpc/client/config/GrpcChannelPropertiesConfig.java
rename to grpc-client-spring-boot-starter/src/test/java/net/devh/boot/grpc/client/config/GrpcChannelPropertiesConfig.java
diff --git a/grpc-client-spring-boot-autoconfigure/src/test/java/net/devh/boot/grpc/client/config/GrpcChannelPropertiesGivenUnitTest.java b/grpc-client-spring-boot-starter/src/test/java/net/devh/boot/grpc/client/config/GrpcChannelPropertiesGivenUnitTest.java
similarity index 89%
rename from grpc-client-spring-boot-autoconfigure/src/test/java/net/devh/boot/grpc/client/config/GrpcChannelPropertiesGivenUnitTest.java
rename to grpc-client-spring-boot-starter/src/test/java/net/devh/boot/grpc/client/config/GrpcChannelPropertiesGivenUnitTest.java
index 4403eef3d..21f7c47f1 100644
--- a/grpc-client-spring-boot-autoconfigure/src/test/java/net/devh/boot/grpc/client/config/GrpcChannelPropertiesGivenUnitTest.java
+++ b/grpc-client-spring-boot-starter/src/test/java/net/devh/boot/grpc/client/config/GrpcChannelPropertiesGivenUnitTest.java
@@ -33,7 +33,8 @@
@ExtendWith(SpringExtension.class)
@SpringBootTest(properties = {
"grpc.client.test.keepAliveTime=42m",
- "grpc.client.test.maxInboundMessageSize=5MB"
+ "grpc.client.test.maxInboundMessageSize=5MB",
+ "grpc.client.test.maxInboundMetadataSize=3MB"
})
class GrpcChannelPropertiesGivenUnitTest {
@@ -45,6 +46,7 @@ void test() {
final GrpcChannelProperties properties = this.grpcChannelsProperties.getChannel("test");
assertEquals(Duration.ofMinutes(42), properties.getKeepAliveTime());
assertEquals(DataSize.ofMegabytes(5), properties.getMaxInboundMessageSize());
+ assertEquals(DataSize.ofMegabytes(3), properties.getMaxInboundMetadataSize());
}
}
diff --git a/grpc-client-spring-boot-autoconfigure/src/test/java/net/devh/boot/grpc/client/config/GrpcChannelPropertiesGlobalTest.java b/grpc-client-spring-boot-starter/src/test/java/net/devh/boot/grpc/client/config/GrpcChannelPropertiesGlobalTest.java
similarity index 81%
rename from grpc-client-spring-boot-autoconfigure/src/test/java/net/devh/boot/grpc/client/config/GrpcChannelPropertiesGlobalTest.java
rename to grpc-client-spring-boot-starter/src/test/java/net/devh/boot/grpc/client/config/GrpcChannelPropertiesGlobalTest.java
index 162c6b715..dfe569f11 100644
--- a/grpc-client-spring-boot-autoconfigure/src/test/java/net/devh/boot/grpc/client/config/GrpcChannelPropertiesGlobalTest.java
+++ b/grpc-client-spring-boot-starter/src/test/java/net/devh/boot/grpc/client/config/GrpcChannelPropertiesGlobalTest.java
@@ -26,6 +26,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.util.unit.DataSize;
/**
* Tests whether the global property fallback works.
@@ -34,6 +35,8 @@
@SpringBootTest(properties = {
"grpc.client.GLOBAL.keepAliveTime=23m",
"grpc.client.GLOBAL.keepAliveTimeout=31s",
+ "grpc.client.GLOBAL.maxInboundMessageSize=5MB",
+ "grpc.client.GLOBAL.maxInboundMetadataSize=3MB",
"grpc.client.test.keepAliveTime=42m"})
class GrpcChannelPropertiesGlobalTest {
@@ -52,4 +55,11 @@ void test() {
assertEquals(Duration.ofSeconds(31), this.grpcChannelsProperties.getChannel("test").getKeepAliveTimeout());
}
+ @Test
+ void testCopyDefaults() {
+ assertEquals(DataSize.ofMegabytes(5),
+ this.grpcChannelsProperties.getChannel("test").getMaxInboundMessageSize());
+ assertEquals(DataSize.ofMegabytes(3),
+ this.grpcChannelsProperties.getChannel("test").getMaxInboundMetadataSize());
+ }
}
diff --git a/grpc-client-spring-boot-autoconfigure/src/test/java/net/devh/boot/grpc/client/config/GrpcChannelPropertiesNegativeGivenUnitTest.java b/grpc-client-spring-boot-starter/src/test/java/net/devh/boot/grpc/client/config/GrpcChannelPropertiesNegativeGivenUnitTest.java
similarity index 100%
rename from grpc-client-spring-boot-autoconfigure/src/test/java/net/devh/boot/grpc/client/config/GrpcChannelPropertiesNegativeGivenUnitTest.java
rename to grpc-client-spring-boot-starter/src/test/java/net/devh/boot/grpc/client/config/GrpcChannelPropertiesNegativeGivenUnitTest.java
diff --git a/grpc-client-spring-boot-autoconfigure/src/test/java/net/devh/boot/grpc/client/config/GrpcChannelPropertiesNegativeNoUnitTest.java b/grpc-client-spring-boot-starter/src/test/java/net/devh/boot/grpc/client/config/GrpcChannelPropertiesNegativeNoUnitTest.java
similarity index 100%
rename from grpc-client-spring-boot-autoconfigure/src/test/java/net/devh/boot/grpc/client/config/GrpcChannelPropertiesNegativeNoUnitTest.java
rename to grpc-client-spring-boot-starter/src/test/java/net/devh/boot/grpc/client/config/GrpcChannelPropertiesNegativeNoUnitTest.java
diff --git a/grpc-client-spring-boot-autoconfigure/src/test/java/net/devh/boot/grpc/client/config/GrpcChannelPropertiesNoUnitTest.java b/grpc-client-spring-boot-starter/src/test/java/net/devh/boot/grpc/client/config/GrpcChannelPropertiesNoUnitTest.java
similarity index 89%
rename from grpc-client-spring-boot-autoconfigure/src/test/java/net/devh/boot/grpc/client/config/GrpcChannelPropertiesNoUnitTest.java
rename to grpc-client-spring-boot-starter/src/test/java/net/devh/boot/grpc/client/config/GrpcChannelPropertiesNoUnitTest.java
index 09cbe7521..71316c492 100644
--- a/grpc-client-spring-boot-autoconfigure/src/test/java/net/devh/boot/grpc/client/config/GrpcChannelPropertiesNoUnitTest.java
+++ b/grpc-client-spring-boot-starter/src/test/java/net/devh/boot/grpc/client/config/GrpcChannelPropertiesNoUnitTest.java
@@ -33,7 +33,8 @@
@ExtendWith(SpringExtension.class)
@SpringBootTest(properties = {
"grpc.client.test.keepAliveTime=42",
- "grpc.client.test.maxInboundMessageSize=5242880"
+ "grpc.client.test.maxInboundMessageSize=5242880",
+ "grpc.client.test.maxInboundMetadataSize=3145728"
})
class GrpcChannelPropertiesNoUnitTest {
@@ -45,6 +46,7 @@ void test() {
final GrpcChannelProperties properties = this.grpcChannelsProperties.getChannel("test");
assertEquals(Duration.ofSeconds(42), properties.getKeepAliveTime());
assertEquals(DataSize.ofMegabytes(5), properties.getMaxInboundMessageSize());
+ assertEquals(DataSize.ofMegabytes(3), properties.getMaxInboundMetadataSize());
}
}
diff --git a/grpc-client-spring-boot-autoconfigure/src/test/java/net/devh/boot/grpc/client/inject/GrpcClientBeanPostProcessorTest.java b/grpc-client-spring-boot-starter/src/test/java/net/devh/boot/grpc/client/inject/GrpcClientBeanPostProcessorTest.java
similarity index 100%
rename from grpc-client-spring-boot-autoconfigure/src/test/java/net/devh/boot/grpc/client/inject/GrpcClientBeanPostProcessorTest.java
rename to grpc-client-spring-boot-starter/src/test/java/net/devh/boot/grpc/client/inject/GrpcClientBeanPostProcessorTest.java
diff --git a/grpc-client-spring-boot-starter/src/test/java/net/devh/boot/grpc/client/metrics/FakeClock.java b/grpc-client-spring-boot-starter/src/test/java/net/devh/boot/grpc/client/metrics/FakeClock.java
new file mode 100644
index 000000000..34fdfe53f
--- /dev/null
+++ b/grpc-client-spring-boot-starter/src/test/java/net/devh/boot/grpc/client/metrics/FakeClock.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2016-2023 The gRPC-Spring 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 net.devh.boot.grpc.client.metrics;
+
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+
+import com.google.common.base.Stopwatch;
+import com.google.common.base.Ticker;
+
+/**
+ * A manipulated clock that exports a {@link com.google.common.base.Ticker}.
+ */
+public final class FakeClock {
+ private long currentTimeNanos;
+ private final Ticker ticker =
+ new Ticker() {
+ @Override
+ public long read() {
+ return currentTimeNanos;
+ }
+ };
+
+ private final Supplier stopwatchSupplier = () -> Stopwatch.createUnstarted(ticker);
+
+ /**
+ * Forward the time by the given duration.
+ */
+ public void forwardTime(long value, TimeUnit unit) {
+ currentTimeNanos += unit.toNanos(value);
+ }
+
+ /**
+ * Provides a stopwatch instance that uses the fake clock ticker.
+ */
+ public Supplier getStopwatchSupplier() {
+ return stopwatchSupplier;
+ }
+}
diff --git a/grpc-client-spring-boot-starter/src/test/java/net/devh/boot/grpc/client/metrics/MetricsClientInterceptorTest.java b/grpc-client-spring-boot-starter/src/test/java/net/devh/boot/grpc/client/metrics/MetricsClientInterceptorTest.java
new file mode 100644
index 000000000..07342374f
--- /dev/null
+++ b/grpc-client-spring-boot-starter/src/test/java/net/devh/boot/grpc/client/metrics/MetricsClientInterceptorTest.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (c) 2016-2024 The gRPC-Spring 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 net.devh.boot.grpc.client.metrics;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.InputStream;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import io.grpc.CallOptions;
+import io.grpc.Channel;
+import io.grpc.ClientCall;
+import io.grpc.ClientInterceptor;
+import io.grpc.ClientInterceptors;
+import io.grpc.ManagedChannel;
+import io.grpc.MethodDescriptor;
+import io.grpc.inprocess.InProcessChannelBuilder;
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.Metrics;
+import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
+
+/**
+ * Tests for {@link MetricsClientInterceptor}.
+ */
+public class MetricsClientInterceptorTest {
+ private static final CallOptions.Key CUSTOM_OPTION =
+ CallOptions.Key.createWithDefault("option1", "default");
+ private static final CallOptions CALL_OPTIONS =
+ CallOptions.DEFAULT.withOption(CUSTOM_OPTION, "customvalue");
+ private static final String FULL_METHOD_NAME = "package1.service1/method1";
+
+ private static class StringInputStream extends InputStream {
+ final String string;
+
+ StringInputStream(String string) {
+ this.string = string;
+ }
+
+ @Override
+ public int read() {
+ // InProcessTransport doesn't actually read bytes from the InputStream. The InputStream is
+ // passed to the InProcess server and consumed by MARSHALLER.parse().
+ throw new UnsupportedOperationException("Should not be called");
+ }
+ }
+
+ private static final MethodDescriptor.Marshaller MARSHALLER =
+ new MethodDescriptor.Marshaller() {
+ @Override
+ public InputStream stream(String value) {
+ return new StringInputStream(value);
+ }
+
+ @Override
+ public String parse(InputStream stream) {
+ return ((StringInputStream) stream).string;
+ }
+ };
+ private final MethodDescriptor method =
+ MethodDescriptor.newBuilder()
+ .setType(MethodDescriptor.MethodType.UNKNOWN)
+ .setRequestMarshaller(MARSHALLER)
+ .setResponseMarshaller(MARSHALLER)
+ .setFullMethodName(FULL_METHOD_NAME)
+ .build();
+
+ private FakeClock fakeClock;
+ private MeterRegistry meterRegistry;
+ private ManagedChannel channel;
+
+ @BeforeEach
+ void setUp() {
+ fakeClock = new FakeClock();
+ meterRegistry = new SimpleMeterRegistry();
+ Metrics.globalRegistry.add(meterRegistry);
+ channel = InProcessChannelBuilder.forName("test").build();
+ }
+
+ @AfterEach
+ void tearDown() {
+ meterRegistry.clear();
+ Metrics.globalRegistry.clear();
+ channel.shutdownNow();
+ }
+
+ @Test
+ void testClientInterceptors() {
+ final AtomicReference capturedCallOptions = new AtomicReference<>();
+ ClientInterceptor callOptionsCaptureInterceptor = new ClientInterceptor() {
+ @Override
+ public ClientCall interceptCall(
+ MethodDescriptor method, CallOptions callOptions, Channel next) {
+ capturedCallOptions.set(callOptions);
+ return next.newCall(method, callOptions);
+ }
+ };
+ Channel interceptedChannel =
+ ClientInterceptors.intercept(
+ channel, callOptionsCaptureInterceptor,
+ new MetricsClientInterceptor(meterRegistry, fakeClock.getStopwatchSupplier()));
+ ClientCall call;
+ call = interceptedChannel.newCall(method, CALL_OPTIONS);
+
+ assertThat(capturedCallOptions.get().getOption(CUSTOM_OPTION)).isEqualTo("customvalue");
+ assertThat(capturedCallOptions.get().getStreamTracerFactories().size()).isEqualTo(1);
+ assertThat(capturedCallOptions.get().getStreamTracerFactories()
+ .get(0) instanceof MetricsClientStreamTracers.CallAttemptsTracerFactory).isTrue();
+ }
+
+}
diff --git a/grpc-client-spring-boot-starter/src/test/java/net/devh/boot/grpc/client/metrics/MetricsClientStreamTracersTest.java b/grpc-client-spring-boot-starter/src/test/java/net/devh/boot/grpc/client/metrics/MetricsClientStreamTracersTest.java
new file mode 100644
index 000000000..e21bd2098
--- /dev/null
+++ b/grpc-client-spring-boot-starter/src/test/java/net/devh/boot/grpc/client/metrics/MetricsClientStreamTracersTest.java
@@ -0,0 +1,473 @@
+/*
+ * Copyright (c) 2016-2023 The gRPC-Spring 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 net.devh.boot.grpc.client.metrics;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.InputStream;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import io.grpc.CallOptions;
+import io.grpc.ClientStreamTracer;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.Status;
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.Metrics;
+import io.micrometer.core.instrument.Tags;
+import io.micrometer.core.instrument.distribution.CountAtBucket;
+import io.micrometer.core.instrument.distribution.HistogramSnapshot;
+import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
+import net.devh.boot.grpc.client.metrics.MetricsClientStreamTracers.CallAttemptsTracerFactory;
+import net.devh.boot.grpc.common.util.Constants;
+
+/**
+ * Tests for {@link MetricsClientStreamTracers}.
+ */
+class MetricsClientStreamTracersTest {
+ private static final CallOptions.Key CUSTOM_OPTION =
+ CallOptions.Key.createWithDefault("option1", "default");
+ private static final CallOptions CALL_OPTIONS =
+ CallOptions.DEFAULT.withOption(CUSTOM_OPTION, "customvalue");
+ private static final ClientStreamTracer.StreamInfo STREAM_INFO =
+ ClientStreamTracer.StreamInfo.newBuilder().setCallOptions(CALL_OPTIONS).build();
+
+ private static final String CLIENT_ATTEMPT_STARTED = "grpc.client.attempt.started";
+ private static final String CLIENT_ATTEMPT_SENT_COMPRESSED_MESSAGE_SIZE =
+ "grpc.client.attempt.sent_total_compressed_message_size";
+ private static final String CLIENT_ATTEMPT_RECEIVED_COMPRESSED_MESSAGE_SIZE =
+ "grpc.client.attempt.rcvd_total_compressed_message_size";
+ private static final String CLIENT_ATTEMPT_DURATION =
+ "grpc.client.attempt.duration";
+ private static final String CLIENT_CALL_DURATION =
+ "grpc.client.call.duration";
+ private static final String GRPC_METHOD_TAG_KEY = "grpc.method";
+ private static final String GRPC_STATUS_TAG_KEY = "grpc.status";
+ private static final String FULL_METHOD_NAME = "package1.service1/method1";
+ private static final String INSTRUMENTATION_SOURCE_TAG_KEY = "instrumentation_source";
+ private static final String INSTRUMENTATION_SOURCE_TAG_VALUE = Constants.LIBRARY_NAME;
+ private static final String INSTRUMENTATION_VERSION_TAG_KEY = "instrumentation_version";
+ private static final String INSTRUMENTATION_VERSION_TAG_VALUE = Constants.VERSION;
+
+ private static class StringInputStream extends InputStream {
+ final String string;
+
+ StringInputStream(String string) {
+ this.string = string;
+ }
+
+ @Override
+ public int read() {
+ // InProcessTransport doesn't actually read bytes from the InputStream. The InputStream is
+ // passed to the InProcess server and consumed by MARSHALLER.parse().
+ throw new UnsupportedOperationException("Should not be called");
+ }
+ }
+
+ private static final MethodDescriptor.Marshaller MARSHALLER =
+ new MethodDescriptor.Marshaller() {
+ @Override
+ public InputStream stream(String value) {
+ return new StringInputStream(value);
+ }
+
+ @Override
+ public String parse(InputStream stream) {
+ return ((StringInputStream) stream).string;
+ }
+ };
+ private final MethodDescriptor method =
+ MethodDescriptor.newBuilder()
+ .setType(MethodDescriptor.MethodType.UNKNOWN)
+ .setRequestMarshaller(MARSHALLER)
+ .setResponseMarshaller(MARSHALLER)
+ .setFullMethodName(FULL_METHOD_NAME)
+ .build();
+
+ private FakeClock fakeClock;
+ private MeterRegistry meterRegistry;
+
+ @BeforeEach
+ void setUp() {
+ fakeClock = new FakeClock();
+ meterRegistry = new SimpleMeterRegistry();
+ Metrics.globalRegistry.add(meterRegistry);
+ }
+
+ @AfterEach
+ void tearDown() {
+ meterRegistry.clear();
+ Metrics.globalRegistry.clear();
+ }
+
+ @Test
+ void clientBasicMetrics() {
+ MetricsClientStreamTracers module =
+ new MetricsClientStreamTracers(fakeClock.getStopwatchSupplier());
+ MetricsClientMeters clientMeters = MetricsClientInstruments.newClientMetricsMeters(meterRegistry);
+ MetricsClientStreamTracers.CallAttemptsTracerFactory callAttemptsTracerFactory =
+ new CallAttemptsTracerFactory(module, method.getFullMethodName(), clientMeters);
+ ClientStreamTracer tracer =
+ callAttemptsTracerFactory.newClientStreamTracer(STREAM_INFO, new Metadata());
+
+ assertThat(meterRegistry.get(CLIENT_ATTEMPT_STARTED)
+ .tag(GRPC_METHOD_TAG_KEY, FULL_METHOD_NAME)
+ .tag(INSTRUMENTATION_SOURCE_TAG_KEY, INSTRUMENTATION_SOURCE_TAG_VALUE)
+ .tag(INSTRUMENTATION_VERSION_TAG_KEY, INSTRUMENTATION_VERSION_TAG_VALUE)
+ .counter()
+ .count()).isEqualTo(1);
+
+ fakeClock.forwardTime(30, MILLISECONDS);
+ tracer.outboundHeaders();
+ tracer.outboundMessage(0);
+ tracer.outboundWireSize(1028);
+
+ fakeClock.forwardTime(100, MILLISECONDS);
+ tracer.outboundMessage(1);
+ tracer.outboundWireSize(99);
+
+ fakeClock.forwardTime(24, MILLISECONDS);
+ tracer.inboundMessage(0);
+ tracer.inboundMessage(1);
+ tracer.inboundWireSize(111);
+ tracer.streamClosed(Status.OK);
+ callAttemptsTracerFactory.callEnded(Status.OK);
+
+ assertThat(meterRegistry.get(CLIENT_ATTEMPT_STARTED)
+ .tag(GRPC_METHOD_TAG_KEY, FULL_METHOD_NAME)
+ .tag(INSTRUMENTATION_SOURCE_TAG_KEY, INSTRUMENTATION_SOURCE_TAG_VALUE)
+ .tag(INSTRUMENTATION_VERSION_TAG_KEY, INSTRUMENTATION_VERSION_TAG_VALUE)
+ .counter()
+ .count()).isEqualTo(1);
+
+ Tags expectedTags =
+ Tags.of(GRPC_METHOD_TAG_KEY, FULL_METHOD_NAME,
+ GRPC_STATUS_TAG_KEY, Status.Code.OK.toString(),
+ INSTRUMENTATION_SOURCE_TAG_KEY, INSTRUMENTATION_SOURCE_TAG_VALUE,
+ INSTRUMENTATION_VERSION_TAG_KEY, INSTRUMENTATION_VERSION_TAG_VALUE);
+
+ HistogramSnapshot attemptDurationSnapshot = meterRegistry.get(CLIENT_ATTEMPT_DURATION)
+ .tags(expectedTags)
+ .timer()
+ .takeSnapshot();
+ HistogramSnapshot attemptDurationHistogram = HistogramSnapshot.empty(1L, 154L, 1.54E8);
+ verifyHistogramSnapshot(true, attemptDurationSnapshot, attemptDurationHistogram,
+ new CountAtBucket(1.6E8, 1));
+
+ HistogramSnapshot callDurationSnapshot = meterRegistry.get(CLIENT_CALL_DURATION)
+ .tags(expectedTags)
+ .timer()
+ .takeSnapshot();
+ HistogramSnapshot expectedCallDurationHistogram = HistogramSnapshot.empty(1L, 154L, 1.54E8);
+ verifyHistogramSnapshot(true, callDurationSnapshot, expectedCallDurationHistogram,
+ new CountAtBucket(1.6E8, 1));
+
+ HistogramSnapshot sentAttemptMessageSizeSnapShot =
+ meterRegistry.get(CLIENT_ATTEMPT_SENT_COMPRESSED_MESSAGE_SIZE)
+ .tags(expectedTags)
+ .summary()
+ .takeSnapshot();
+ HistogramSnapshot expectedAttemptSentMessageSizeHistogram = HistogramSnapshot.empty(1L, 1127L, 1127L);
+ verifyHistogramSnapshot(false, sentAttemptMessageSizeSnapShot, expectedAttemptSentMessageSizeHistogram,
+ new CountAtBucket(2048.0, 1));
+
+ HistogramSnapshot receivedAttemptMessageSizeSnapShot =
+ meterRegistry.get(CLIENT_ATTEMPT_RECEIVED_COMPRESSED_MESSAGE_SIZE)
+ .tags(expectedTags)
+ .summary()
+ .takeSnapshot();
+ HistogramSnapshot expectedAttemptReceivedMessageSizeHistogram = HistogramSnapshot.empty(1L, 111L, 111L);
+ verifyHistogramSnapshot(false, receivedAttemptMessageSizeSnapShot, expectedAttemptReceivedMessageSizeHistogram,
+ new CountAtBucket(1024.0, 1));
+ }
+
+ // This test is only unit-testing teh metrics recording logic. Retry behavior is faked.
+ @Test
+ void recordAttemptMetrics() {
+ MetricsClientStreamTracers module =
+ new MetricsClientStreamTracers(fakeClock.getStopwatchSupplier());
+ MetricsClientMeters clientMeters = MetricsClientInstruments.newClientMetricsMeters(meterRegistry);
+ MetricsClientStreamTracers.CallAttemptsTracerFactory callAttemptsTracerFactory =
+ new CallAttemptsTracerFactory(module, method.getFullMethodName(), clientMeters);
+ ClientStreamTracer tracer =
+ callAttemptsTracerFactory.newClientStreamTracer(STREAM_INFO, new Metadata());
+
+ assertThat(meterRegistry.get(CLIENT_ATTEMPT_STARTED)
+ .tag(GRPC_METHOD_TAG_KEY, FULL_METHOD_NAME)
+ .tag(INSTRUMENTATION_SOURCE_TAG_KEY, INSTRUMENTATION_SOURCE_TAG_VALUE)
+ .tag(INSTRUMENTATION_VERSION_TAG_KEY, INSTRUMENTATION_VERSION_TAG_VALUE)
+ .counter()
+ .count()).isEqualTo(1);
+
+ fakeClock.forwardTime(60, MILLISECONDS);
+ tracer.outboundHeaders();
+ fakeClock.forwardTime(120, MILLISECONDS);
+ tracer.outboundMessage(0);
+ tracer.outboundMessage(1);
+ tracer.outboundWireSize(1028);
+ fakeClock.forwardTime(24, MILLISECONDS);
+ tracer.streamClosed(Status.UNAVAILABLE);
+
+ Tags expectedUnailableStatusTags =
+ Tags.of(GRPC_METHOD_TAG_KEY, FULL_METHOD_NAME,
+ GRPC_STATUS_TAG_KEY, Status.Code.UNAVAILABLE.toString(),
+ INSTRUMENTATION_SOURCE_TAG_KEY, INSTRUMENTATION_SOURCE_TAG_VALUE,
+ INSTRUMENTATION_VERSION_TAG_KEY, INSTRUMENTATION_VERSION_TAG_VALUE);
+
+ assertThat(meterRegistry.get(CLIENT_ATTEMPT_STARTED)
+ .tag(GRPC_METHOD_TAG_KEY, FULL_METHOD_NAME)
+ .tag(INSTRUMENTATION_SOURCE_TAG_KEY, INSTRUMENTATION_SOURCE_TAG_VALUE)
+ .tag(INSTRUMENTATION_VERSION_TAG_KEY, INSTRUMENTATION_VERSION_TAG_VALUE)
+ .counter()
+ .count()).isEqualTo(1);
+ assertThat(meterRegistry.get(CLIENT_ATTEMPT_DURATION)
+ .tags(expectedUnailableStatusTags)
+ .timer()
+ .takeSnapshot()
+ .total(MILLISECONDS)).isEqualTo(60L + 120L + 24L);
+ assertThat(meterRegistry.get(CLIENT_ATTEMPT_SENT_COMPRESSED_MESSAGE_SIZE)
+ .tags(expectedUnailableStatusTags)
+ .summary()
+ .takeSnapshot()
+ .total()).isEqualTo(1028L);
+ assertThat(meterRegistry.get(CLIENT_ATTEMPT_RECEIVED_COMPRESSED_MESSAGE_SIZE)
+ .tags(expectedUnailableStatusTags)
+ .summary()
+ .takeSnapshot()
+ .total()).isEqualTo(0);
+
+ // Faking retry
+ fakeClock.forwardTime(1200, MILLISECONDS);
+
+ tracer = callAttemptsTracerFactory.newClientStreamTracer(STREAM_INFO, new Metadata());
+
+ tracer.outboundHeaders();
+ tracer.outboundMessage(0);
+ tracer.outboundMessage(1);
+ tracer.outboundWireSize(1028);
+ fakeClock.forwardTime(100, MILLISECONDS);
+ tracer.streamClosed(Status.NOT_FOUND);
+
+ Tags expectedNotFoundStatusTags =
+ Tags.of(GRPC_METHOD_TAG_KEY, FULL_METHOD_NAME,
+ GRPC_STATUS_TAG_KEY, Status.Code.NOT_FOUND.toString(),
+ INSTRUMENTATION_SOURCE_TAG_KEY, INSTRUMENTATION_SOURCE_TAG_VALUE,
+ INSTRUMENTATION_VERSION_TAG_KEY, INSTRUMENTATION_VERSION_TAG_VALUE);
+
+ assertThat(meterRegistry.get(CLIENT_ATTEMPT_STARTED)
+ .tag(GRPC_METHOD_TAG_KEY, FULL_METHOD_NAME)
+ .tag(INSTRUMENTATION_SOURCE_TAG_KEY, INSTRUMENTATION_SOURCE_TAG_VALUE)
+ .tag(INSTRUMENTATION_VERSION_TAG_KEY, INSTRUMENTATION_VERSION_TAG_VALUE)
+ .counter()
+ .count()).isEqualTo(2);
+
+ HistogramSnapshot secondAttemptDurationSnapshot = meterRegistry.get(CLIENT_ATTEMPT_DURATION)
+ .tags(expectedNotFoundStatusTags)
+ .timer()
+ .takeSnapshot();
+ HistogramSnapshot secondExpectedAttemptDurationHistogram = HistogramSnapshot.empty(1L, 100L, 1.0E8);
+ verifyHistogramSnapshot(true, secondAttemptDurationSnapshot, secondExpectedAttemptDurationHistogram,
+ new CountAtBucket(1.0E8, 1));
+
+ HistogramSnapshot secondSentAttemptMessageSizeSnapShot =
+ meterRegistry.get(CLIENT_ATTEMPT_SENT_COMPRESSED_MESSAGE_SIZE)
+ .tags(expectedNotFoundStatusTags)
+ .summary()
+ .takeSnapshot();
+ HistogramSnapshot secondExpectedAttemptSentMessageSizeHistogram = HistogramSnapshot.empty(1L, 1028L, 1028.0);
+ verifyHistogramSnapshot(false, secondSentAttemptMessageSizeSnapShot,
+ secondExpectedAttemptSentMessageSizeHistogram,
+ new CountAtBucket(2048.0, 1));
+
+ assertThat(meterRegistry.get(CLIENT_ATTEMPT_RECEIVED_COMPRESSED_MESSAGE_SIZE)
+ .tags(expectedNotFoundStatusTags)
+ .summary()
+ .takeSnapshot()
+ .total()).isEqualTo(0);
+
+ // fake transparent retry
+ fakeClock.forwardTime(100, MILLISECONDS);
+
+ tracer = callAttemptsTracerFactory.newClientStreamTracer(
+ STREAM_INFO.toBuilder().setIsTransparentRetry(true).build(), new Metadata());
+
+ fakeClock.forwardTime(32, MILLISECONDS);
+ tracer.streamClosed(Status.UNAVAILABLE);
+
+ assertThat(meterRegistry.get(CLIENT_ATTEMPT_STARTED)
+ .tag(GRPC_METHOD_TAG_KEY, FULL_METHOD_NAME)
+ .tag(INSTRUMENTATION_SOURCE_TAG_KEY, INSTRUMENTATION_SOURCE_TAG_VALUE)
+ .tag(INSTRUMENTATION_VERSION_TAG_KEY, INSTRUMENTATION_VERSION_TAG_VALUE)
+ .counter()
+ .count()).isEqualTo(3);
+
+ HistogramSnapshot thirdAttemptDurationSnapshot = meterRegistry.get(CLIENT_ATTEMPT_DURATION)
+ .tags(expectedUnailableStatusTags)
+ .timer()
+ .takeSnapshot();
+ HistogramSnapshot thirdExpectedAttemptDurationHistogram = HistogramSnapshot.empty(2L, 204L + 32L, 2.04E8);
+ verifyHistogramSnapshot(true, thirdAttemptDurationSnapshot, thirdExpectedAttemptDurationHistogram,
+ new CountAtBucket(4.0E7, 1));
+
+ HistogramSnapshot thirdSentAttemptMessageSizeSnapShot =
+ meterRegistry.get(CLIENT_ATTEMPT_SENT_COMPRESSED_MESSAGE_SIZE)
+ .tags(expectedUnailableStatusTags)
+ .summary()
+ .takeSnapshot();
+ HistogramSnapshot thirdExpectedAttemptSentMessageSizeHistogram = HistogramSnapshot.empty(2L, 1028L + 0, 1028.0);
+ verifyHistogramSnapshot(false, thirdSentAttemptMessageSizeSnapShot,
+ thirdExpectedAttemptSentMessageSizeHistogram,
+ new CountAtBucket(2048.0, 2));
+
+ HistogramSnapshot thirdReceivedAttemptMessageSizeSnapShot =
+ meterRegistry.get(CLIENT_ATTEMPT_RECEIVED_COMPRESSED_MESSAGE_SIZE)
+ .tags(expectedUnailableStatusTags)
+ .summary()
+ .takeSnapshot();
+ HistogramSnapshot thirdExpectedAttemptReceivedMessageSizeHistogram = HistogramSnapshot.empty(2L, 0, 0);
+ verifyHistogramSnapshot(false, thirdReceivedAttemptMessageSizeSnapShot,
+ thirdExpectedAttemptReceivedMessageSizeHistogram,
+ new CountAtBucket(1024.0, 2));
+
+ // Fake another transparent retry
+ fakeClock.forwardTime(10, MILLISECONDS);
+
+ tracer = callAttemptsTracerFactory.newClientStreamTracer(
+ STREAM_INFO.toBuilder().setIsTransparentRetry(true).build(), new Metadata());
+
+ tracer.outboundHeaders();
+ tracer.outboundMessage(0);
+ tracer.outboundMessage(1);
+ tracer.outboundWireSize(1028);
+
+ fakeClock.forwardTime(124, MILLISECONDS);
+ tracer.inboundMessage(0);
+ tracer.inboundWireSize(33);
+
+ fakeClock.forwardTime(24, MILLISECONDS);
+ // RPC succeeded
+ tracer.streamClosed(Status.OK);
+ callAttemptsTracerFactory.callEnded(Status.OK);
+
+ Tags expectedOKStatusTags =
+ Tags.of(GRPC_METHOD_TAG_KEY, FULL_METHOD_NAME,
+ GRPC_STATUS_TAG_KEY, Status.Code.OK.toString(),
+ INSTRUMENTATION_SOURCE_TAG_KEY, INSTRUMENTATION_SOURCE_TAG_VALUE,
+ INSTRUMENTATION_VERSION_TAG_KEY, INSTRUMENTATION_VERSION_TAG_VALUE);
+
+ assertThat(meterRegistry.get(CLIENT_ATTEMPT_STARTED)
+ .tag(GRPC_METHOD_TAG_KEY, FULL_METHOD_NAME)
+ .tag(INSTRUMENTATION_SOURCE_TAG_KEY, INSTRUMENTATION_SOURCE_TAG_VALUE)
+ .tag(INSTRUMENTATION_VERSION_TAG_KEY, INSTRUMENTATION_VERSION_TAG_VALUE)
+ .counter()
+ .count()).isEqualTo(4);
+ assertThat(meterRegistry.get(CLIENT_ATTEMPT_DURATION)
+ .tags(expectedOKStatusTags)
+ .timer()
+ .takeSnapshot()
+ .total(MILLISECONDS)).isEqualTo(124L + 24L);
+ assertThat(meterRegistry.get(CLIENT_ATTEMPT_SENT_COMPRESSED_MESSAGE_SIZE)
+ .tags(expectedOKStatusTags)
+ .summary()
+ .takeSnapshot()
+ .total()).isEqualTo(1028L);
+ assertThat(meterRegistry.get(CLIENT_ATTEMPT_RECEIVED_COMPRESSED_MESSAGE_SIZE)
+ .tags(expectedOKStatusTags)
+ .summary()
+ .takeSnapshot()
+ .total()).isEqualTo(33);
+
+ HistogramSnapshot callDurationSnapshot = meterRegistry.get(CLIENT_CALL_DURATION)
+ .tags(expectedOKStatusTags)
+ .timer()
+ .takeSnapshot();
+ HistogramSnapshot expectedCallDurationHistogram =
+ HistogramSnapshot.empty(1L, 60 + 120 + 24 + 1200 + 100 + 100 + 32 + 10 + 124 + 24L, 1.794E9);
+ verifyHistogramSnapshot(true, callDurationSnapshot, expectedCallDurationHistogram,
+ new CountAtBucket(2.0E9, 1));
+ }
+
+ @Test
+ void clientStreamNeverCreatedStillRecordMetrics() {
+ MetricsClientStreamTracers module =
+ new MetricsClientStreamTracers(fakeClock.getStopwatchSupplier());
+ MetricsClientMeters clientMeters = MetricsClientInstruments.newClientMetricsMeters(meterRegistry);
+ MetricsClientStreamTracers.CallAttemptsTracerFactory callAttemptsTracerFactory =
+ new CallAttemptsTracerFactory(module, method.getFullMethodName(), clientMeters);
+
+ fakeClock.forwardTime(3000, MILLISECONDS);
+ Status status = Status.DEADLINE_EXCEEDED.withDescription("5 seconds");
+
+ callAttemptsTracerFactory.callEnded(status);
+
+ Tags expectedDeadlineExceededStatusTags =
+ Tags.of(GRPC_METHOD_TAG_KEY, FULL_METHOD_NAME,
+ GRPC_STATUS_TAG_KEY, Status.Code.DEADLINE_EXCEEDED.toString(),
+ INSTRUMENTATION_SOURCE_TAG_KEY, INSTRUMENTATION_SOURCE_TAG_VALUE,
+ INSTRUMENTATION_VERSION_TAG_KEY, INSTRUMENTATION_VERSION_TAG_VALUE);
+
+ HistogramSnapshot attemptDurationSnapshot = meterRegistry.get(CLIENT_ATTEMPT_DURATION)
+ .tags(expectedDeadlineExceededStatusTags)
+ .timer()
+ .takeSnapshot();
+ HistogramSnapshot expectedAttemptDurationHistogram = HistogramSnapshot.empty(1L, 0, 0);
+ verifyHistogramSnapshot(true, attemptDurationSnapshot, expectedAttemptDurationHistogram,
+ new CountAtBucket(10000.0, 1));
+
+ HistogramSnapshot sentAttemptMessageSizeSnapShot =
+ meterRegistry.get(CLIENT_ATTEMPT_SENT_COMPRESSED_MESSAGE_SIZE)
+ .tags(expectedDeadlineExceededStatusTags)
+ .summary()
+ .takeSnapshot();
+ HistogramSnapshot expectedAttemptSentMessageSizeHistogram = HistogramSnapshot.empty(1L, 0, 0);
+ verifyHistogramSnapshot(false, sentAttemptMessageSizeSnapShot, expectedAttemptSentMessageSizeHistogram,
+ new CountAtBucket(1024.0, 1));
+
+ HistogramSnapshot receivedAttemptMessageSizeSnapShot =
+ meterRegistry.get(CLIENT_ATTEMPT_RECEIVED_COMPRESSED_MESSAGE_SIZE)
+ .tags(expectedDeadlineExceededStatusTags)
+ .summary()
+ .takeSnapshot();
+ HistogramSnapshot expectedAttemptReceivedMessageSizeHistogram = HistogramSnapshot.empty(1L, 0, 0);
+ verifyHistogramSnapshot(false, receivedAttemptMessageSizeSnapShot, expectedAttemptReceivedMessageSizeHistogram,
+ new CountAtBucket(1024.0, 1));
+
+ HistogramSnapshot callDurationSnapshot = meterRegistry.get(CLIENT_CALL_DURATION)
+ .tags(expectedDeadlineExceededStatusTags)
+ .timer()
+ .takeSnapshot();
+ HistogramSnapshot expectedCallDurationHistogram = HistogramSnapshot.empty(1L, 3000, 3.0E9);
+ verifyHistogramSnapshot(true, callDurationSnapshot, expectedCallDurationHistogram,
+ new CountAtBucket(5.0E9, 1));
+ }
+
+ static void verifyHistogramSnapshot(boolean isTimer, HistogramSnapshot actual, HistogramSnapshot expected,
+ CountAtBucket expectedHistogramBucketWithValue) {
+ if (isTimer) {
+ assertThat(actual.total(MILLISECONDS)).isEqualTo(expected.total());
+ } else {
+ assertThat(actual.total()).isEqualTo(expected.total());
+ }
+ assertThat(actual.count()).isEqualTo(expected.count());
+ assertThat(actual.max()).isEqualTo(expected.max());
+ assertThat(actual.histogramCounts()).contains(expectedHistogramBucketWithValue);
+ }
+}
diff --git a/grpc-client-spring-boot-starter/src/test/java/net/devh/boot/grpc/client/nameresolver/DiscoveryClientNameResolverTest.java b/grpc-client-spring-boot-starter/src/test/java/net/devh/boot/grpc/client/nameresolver/DiscoveryClientNameResolverTest.java
new file mode 100644
index 000000000..f91eda7ca
--- /dev/null
+++ b/grpc-client-spring-boot-starter/src/test/java/net/devh/boot/grpc/client/nameresolver/DiscoveryClientNameResolverTest.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (c) 2016-2023 The gRPC-Spring 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 net.devh.boot.grpc.client.nameresolver;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.net.InetSocketAddress;
+import java.util.List;
+import java.util.Map;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.cloud.client.DefaultServiceInstance;
+import org.springframework.cloud.client.discovery.simple.SimpleDiscoveryClient;
+import org.springframework.cloud.client.discovery.simple.SimpleDiscoveryProperties;
+
+import io.grpc.NameResolver;
+import io.grpc.Status;
+import io.grpc.SynchronizationContext;
+import io.grpc.internal.AutoConfiguredLoadBalancerFactory;
+import io.grpc.internal.GrpcUtil;
+import io.grpc.internal.ScParser;
+import net.devh.boot.grpc.common.util.GrpcUtils;
+
+/**
+ * Test for {@link DiscoveryClientNameResolver}.
+ */
+public class DiscoveryClientNameResolverTest {
+
+ private final NameResolver.Args args = NameResolver.Args.newBuilder()
+ .setDefaultPort(1212)
+ .setProxyDetector(GrpcUtil.DEFAULT_PROXY_DETECTOR)
+ .setSynchronizationContext(
+ new SynchronizationContext((t, e) -> {
+ throw new AssertionError(e);
+ }))
+ .setServiceConfigParser(new ScParser(true, 10, 10, new AutoConfiguredLoadBalancerFactory("pick_first")))
+ .setOffloadExecutor(Runnable::run)
+ .build();
+
+ @Test
+ void testValidServiceConfig() {
+ String validServiceConfig = """
+ {
+ "loadBalancingConfig": [
+ {"round_robin": {}}
+ ],
+ "methodConfig": [
+ {
+ "name": [{}],
+ "retryPolicy": {
+ "maxAttempts": 5,
+ "initialBackoff": "0.05s",
+ "maxBackoff": "1s",
+ "backoffMultiplier": 2,
+ "retryableStatusCodes": [
+ "UNAVAILABLE",
+ "ABORTED",
+ "DATA_LOSS",
+ "INTERNAL",
+ "DEADLINE_EXCEEDED"
+ ]
+ },
+ "timeout": "5s"
+ }
+ ]
+ }
+ """;
+ TestableListener listener = resolveServiceAndVerify("test1", validServiceConfig);
+ NameResolver.ConfigOrError serviceConf = listener.getResult().getServiceConfig();
+ assertThat(serviceConf).isNotNull();
+ assertThat(serviceConf.getConfig()).isNotNull();
+ assertThat(serviceConf.getError()).isNull();
+ }
+
+ @Test
+ void testBrokenServiceConfig() {
+ TestableListener listener = resolveServiceAndVerify("test2", "intentionally invalid service config");
+ NameResolver.ConfigOrError serviceConf = listener.getResult().getServiceConfig();
+ assertThat(serviceConf).isNotNull();
+ assertThat(serviceConf.getConfig()).isNull();
+ assertThat(serviceConf.getError()).extracting(Status::getCode).isEqualTo(Status.Code.UNKNOWN);
+ }
+
+ private TestableListener resolveServiceAndVerify(String serviceName, String serviceConfig) {
+ SimpleDiscoveryProperties props = new SimpleDiscoveryProperties();
+ DefaultServiceInstance service = new DefaultServiceInstance(
+ serviceName + "-1", serviceName, "127.0.0.1", 3322, false);
+ Map meta = service.getMetadata();
+ meta.put(GrpcUtils.CLOUD_DISCOVERY_METADATA_PORT, "6688");
+ meta.put(GrpcUtils.CLOUD_DISCOVERY_METADATA_SERVICE_CONFIG, serviceConfig);
+ props.setInstances(Map.of(serviceName, List.of(service)));
+ SimpleDiscoveryClient disco = new SimpleDiscoveryClient(props);
+ DiscoveryClientNameResolver dcnr = new DiscoveryClientNameResolver(serviceName, disco, args, null, null);
+
+ TestableListener listener = new TestableListener();
+ dcnr.start(listener);
+
+ assertThat(listener.isErrorWasSet()).isFalse();
+ assertThat(listener.isResultWasSet()).isTrue();
+ InetSocketAddress addr = (InetSocketAddress) listener.getResult().getAddresses().get(0).getAddresses().get(0);
+ assertThat(addr.getPort()).isEqualTo(6688);
+ assertThat(addr.getHostString()).isEqualTo("127.0.0.1");
+ return listener;
+ }
+}
diff --git a/grpc-client-spring-boot-starter/src/test/java/net/devh/boot/grpc/client/nameresolver/TestableListener.java b/grpc-client-spring-boot-starter/src/test/java/net/devh/boot/grpc/client/nameresolver/TestableListener.java
new file mode 100644
index 000000000..135d5ed52
--- /dev/null
+++ b/grpc-client-spring-boot-starter/src/test/java/net/devh/boot/grpc/client/nameresolver/TestableListener.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (c) 2016-2023 The gRPC-Spring 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 net.devh.boot.grpc.client.nameresolver;
+
+import io.grpc.NameResolver;
+import io.grpc.Status;
+import lombok.Getter;
+
+@Getter
+public class TestableListener extends NameResolver.Listener2 {
+
+ private NameResolver.ResolutionResult result;
+ private Status error;
+ private boolean resultWasSet = false;
+ private boolean errorWasSet = false;
+
+ @Override
+ public void onResult(NameResolver.ResolutionResult resolutionResult) {
+ this.result = resolutionResult;
+ resultWasSet = true;
+ }
+
+ @Override
+ public void onError(Status error) {
+ this.error = error;
+ errorWasSet = true;
+ }
+
+}
diff --git a/grpc-common-spring-boot/src/main/java/net/devh/boot/grpc/common/util/Constants.java b/grpc-common-spring-boot/src/main/java/net/devh/boot/grpc/common/util/Constants.java
new file mode 100644
index 000000000..d2fe16943
--- /dev/null
+++ b/grpc-common-spring-boot/src/main/java/net/devh/boot/grpc/common/util/Constants.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2016-2023 The gRPC-Spring 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 net.devh.boot.grpc.common.util;
+
+/**
+ * Class that contains shared constants.
+ */
+public final class Constants {
+
+ /**
+ * A constant that defines the current version of the library.
+ */
+ public static final String VERSION = "v" + Constants.class.getPackage().getImplementationVersion();
+
+ /**
+ * A constant that defines the library name that can be used as metric tags.
+ */
+ public static final String LIBRARY_NAME = "grpc-spring";
+
+ private Constants() {}
+
+}
diff --git a/grpc-common-spring-boot/src/main/java/net/devh/boot/grpc/common/util/GrpcUtils.java b/grpc-common-spring-boot/src/main/java/net/devh/boot/grpc/common/util/GrpcUtils.java
index f2583258e..1aafdd761 100644
--- a/grpc-common-spring-boot/src/main/java/net/devh/boot/grpc/common/util/GrpcUtils.java
+++ b/grpc-common-spring-boot/src/main/java/net/devh/boot/grpc/common/util/GrpcUtils.java
@@ -40,6 +40,11 @@ public final class GrpcUtils {
*/
public static final String CLOUD_DISCOVERY_METADATA_PORT = "gRPC_port";
+ /**
+ * The cloud discovery metadata key used to identify service config.
+ */
+ public static final String CLOUD_DISCOVERY_METADATA_SERVICE_CONFIG = "gRPC_service_config";
+
/**
* The constant for the grpc server port, -1 represents don't start an inter process server.
*/
diff --git a/grpc-server-spring-boot-autoconfigure/build.gradle b/grpc-server-spring-boot-autoconfigure/build.gradle
deleted file mode 100644
index c3f39af1e..000000000
--- a/grpc-server-spring-boot-autoconfigure/build.gradle
+++ /dev/null
@@ -1,36 +0,0 @@
-plugins {
- id 'java-library'
-}
-
-apply from: '../deploy.gradle'
-
-group = 'net.devh'
-version = projectVersion
-
-compileJava.dependsOn(processResources)
-
-dependencies {
- annotationProcessor 'org.springframework.boot:spring-boot-autoconfigure-processor'
- annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
-
- api project(':grpc-common-spring-boot')
- api 'org.springframework.boot:spring-boot-starter'
- optionalSupportImplementation "io.micrometer:micrometer-observation"
- optionalSupportImplementation 'org.springframework.boot:spring-boot-starter-actuator'
- optionalSupportImplementation 'org.springframework.security:spring-security-core'
- optionalSupportImplementation 'org.springframework.cloud:spring-cloud-starter-consul-discovery'
- optionalSupportImplementation 'org.springframework.cloud:spring-cloud-starter-zookeeper-discovery'
- optionalSupportImplementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
- optionalSupportImplementation "com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-discovery"
- optionalSupportImplementation 'io.zipkin.brave:brave-instrumentation-grpc'
- optionalSupportApi 'io.grpc:grpc-netty'
- api 'io.grpc:grpc-inprocess'
- api 'io.grpc:grpc-netty-shaded'
- api 'io.grpc:grpc-protobuf'
- api 'io.grpc:grpc-stub'
- api 'io.grpc:grpc-services'
- api 'io.grpc:grpc-api'
-
- testImplementation 'io.grpc:grpc-testing'
- testImplementation('org.springframework.boot:spring-boot-starter-test')
-}
diff --git a/grpc-server-spring-boot-starter/build.gradle b/grpc-server-spring-boot-starter/build.gradle
index 4ed0c7f63..c3f39af1e 100644
--- a/grpc-server-spring-boot-starter/build.gradle
+++ b/grpc-server-spring-boot-starter/build.gradle
@@ -7,6 +7,30 @@ apply from: '../deploy.gradle'
group = 'net.devh'
version = projectVersion
+compileJava.dependsOn(processResources)
+
dependencies {
- api project(':grpc-server-spring-boot-autoconfigure')
+ annotationProcessor 'org.springframework.boot:spring-boot-autoconfigure-processor'
+ annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
+
+ api project(':grpc-common-spring-boot')
+ api 'org.springframework.boot:spring-boot-starter'
+ optionalSupportImplementation "io.micrometer:micrometer-observation"
+ optionalSupportImplementation 'org.springframework.boot:spring-boot-starter-actuator'
+ optionalSupportImplementation 'org.springframework.security:spring-security-core'
+ optionalSupportImplementation 'org.springframework.cloud:spring-cloud-starter-consul-discovery'
+ optionalSupportImplementation 'org.springframework.cloud:spring-cloud-starter-zookeeper-discovery'
+ optionalSupportImplementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
+ optionalSupportImplementation "com.alibaba.cloud:spring-cloud-starter-alibaba-nacos-discovery"
+ optionalSupportImplementation 'io.zipkin.brave:brave-instrumentation-grpc'
+ optionalSupportApi 'io.grpc:grpc-netty'
+ api 'io.grpc:grpc-inprocess'
+ api 'io.grpc:grpc-netty-shaded'
+ api 'io.grpc:grpc-protobuf'
+ api 'io.grpc:grpc-stub'
+ api 'io.grpc:grpc-services'
+ api 'io.grpc:grpc-api'
+
+ testImplementation 'io.grpc:grpc-testing'
+ testImplementation('org.springframework.boot:spring-boot-starter-test')
}
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/advice/GrpcAdvice.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/advice/GrpcAdvice.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/advice/GrpcAdvice.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/advice/GrpcAdvice.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/advice/GrpcAdviceDiscoverer.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/advice/GrpcAdviceDiscoverer.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/advice/GrpcAdviceDiscoverer.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/advice/GrpcAdviceDiscoverer.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/advice/GrpcAdviceExceptionHandler.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/advice/GrpcAdviceExceptionHandler.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/advice/GrpcAdviceExceptionHandler.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/advice/GrpcAdviceExceptionHandler.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/advice/GrpcAdviceIsPresentCondition.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/advice/GrpcAdviceIsPresentCondition.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/advice/GrpcAdviceIsPresentCondition.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/advice/GrpcAdviceIsPresentCondition.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/advice/GrpcExceptionHandler.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/advice/GrpcExceptionHandler.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/advice/GrpcExceptionHandler.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/advice/GrpcExceptionHandler.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/advice/GrpcExceptionHandlerMethodResolver.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/advice/GrpcExceptionHandlerMethodResolver.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/advice/GrpcExceptionHandlerMethodResolver.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/advice/GrpcExceptionHandlerMethodResolver.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcAdviceAutoConfiguration.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcAdviceAutoConfiguration.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcAdviceAutoConfiguration.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcAdviceAutoConfiguration.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcHealthServiceAutoConfiguration.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcHealthServiceAutoConfiguration.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcHealthServiceAutoConfiguration.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcHealthServiceAutoConfiguration.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcMetadataConsulConfiguration.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcMetadataConsulConfiguration.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcMetadataConsulConfiguration.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcMetadataConsulConfiguration.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcMetadataEurekaConfiguration.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcMetadataEurekaConfiguration.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcMetadataEurekaConfiguration.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcMetadataEurekaConfiguration.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcMetadataNacosConfiguration.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcMetadataNacosConfiguration.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcMetadataNacosConfiguration.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcMetadataNacosConfiguration.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcMetadataZookeeperConfiguration.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcMetadataZookeeperConfiguration.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcMetadataZookeeperConfiguration.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcMetadataZookeeperConfiguration.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcReflectionServiceAutoConfiguration.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcReflectionServiceAutoConfiguration.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcReflectionServiceAutoConfiguration.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcReflectionServiceAutoConfiguration.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcServerAutoConfiguration.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcServerAutoConfiguration.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcServerAutoConfiguration.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcServerAutoConfiguration.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcServerFactoryAutoConfiguration.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcServerFactoryAutoConfiguration.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcServerFactoryAutoConfiguration.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcServerFactoryAutoConfiguration.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcServerMetricAutoConfiguration.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcServerMetricAutoConfiguration.java
similarity index 86%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcServerMetricAutoConfiguration.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcServerMetricAutoConfiguration.java
index 3ca8bdfdd..5f94f95ab 100644
--- a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcServerMetricAutoConfiguration.java
+++ b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcServerMetricAutoConfiguration.java
@@ -33,11 +33,14 @@
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.annotation.Order;
+import com.google.common.base.Stopwatch;
+
import io.grpc.BindableService;
import io.grpc.MethodDescriptor;
import io.grpc.ServiceDescriptor;
@@ -47,6 +50,8 @@
import net.devh.boot.grpc.common.util.InterceptorOrder;
import net.devh.boot.grpc.server.config.GrpcServerProperties;
import net.devh.boot.grpc.server.interceptor.GrpcGlobalServerInterceptor;
+import net.devh.boot.grpc.server.metrics.MetricsServerStreamTracers;
+import net.devh.boot.grpc.server.serverfactory.GrpcServerConfigurer;
/**
* Auto configuration class for Spring-Boot. This allows zero config server metrics for gRPC services.
@@ -75,6 +80,15 @@ public MetricCollectingServerInterceptor metricCollectingServerInterceptor(final
return metricCollector;
}
+ @ConditionalOnProperty(prefix = "grpc", name = "metricsA66Enabled", matchIfMissing = true)
+ @Bean
+ public GrpcServerConfigurer streamTracerFactoryConfigurer(final MeterRegistry registry) {
+ MetricsServerStreamTracers metricsServerStreamTracers = new MetricsServerStreamTracers(
+ Stopwatch::createUnstarted);
+ return builder -> builder
+ .addStreamTracerFactory(metricsServerStreamTracers.getMetricsServerTracerFactory(registry));
+ }
+
@Bean
@Lazy
InfoContributor grpcInfoContributor(final GrpcServerProperties properties,
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcServerMicrometerTraceAutoConfiguration.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcServerMicrometerTraceAutoConfiguration.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcServerMicrometerTraceAutoConfiguration.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcServerMicrometerTraceAutoConfiguration.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcServerSecurityAutoConfiguration.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcServerSecurityAutoConfiguration.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcServerSecurityAutoConfiguration.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/autoconfigure/GrpcServerSecurityAutoConfiguration.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/autoconfigure/package-info.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/autoconfigure/package-info.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/autoconfigure/package-info.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/autoconfigure/package-info.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/condition/ConditionalOnInterprocessServer.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/condition/ConditionalOnInterprocessServer.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/condition/ConditionalOnInterprocessServer.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/condition/ConditionalOnInterprocessServer.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/config/ClientAuth.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/config/ClientAuth.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/config/ClientAuth.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/config/ClientAuth.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/config/GrpcServerProperties.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/config/GrpcServerProperties.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/config/GrpcServerProperties.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/config/GrpcServerProperties.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/config/package-info.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/config/package-info.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/config/package-info.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/config/package-info.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/error/GrpcExceptionInterceptor.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/error/GrpcExceptionInterceptor.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/error/GrpcExceptionInterceptor.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/error/GrpcExceptionInterceptor.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/error/GrpcExceptionListener.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/error/GrpcExceptionListener.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/error/GrpcExceptionListener.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/error/GrpcExceptionListener.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/error/GrpcExceptionResponseHandler.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/error/GrpcExceptionResponseHandler.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/error/GrpcExceptionResponseHandler.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/error/GrpcExceptionResponseHandler.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/error/GrpcExceptionServerCall.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/error/GrpcExceptionServerCall.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/error/GrpcExceptionServerCall.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/error/GrpcExceptionServerCall.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/event/GrpcServerLifecycleEvent.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/event/GrpcServerLifecycleEvent.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/event/GrpcServerLifecycleEvent.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/event/GrpcServerLifecycleEvent.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/event/GrpcServerShutdownEvent.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/event/GrpcServerShutdownEvent.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/event/GrpcServerShutdownEvent.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/event/GrpcServerShutdownEvent.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/event/GrpcServerStartedEvent.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/event/GrpcServerStartedEvent.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/event/GrpcServerStartedEvent.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/event/GrpcServerStartedEvent.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/event/GrpcServerTerminatedEvent.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/event/GrpcServerTerminatedEvent.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/event/GrpcServerTerminatedEvent.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/event/GrpcServerTerminatedEvent.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/event/package-info.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/event/package-info.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/event/package-info.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/event/package-info.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/interceptor/AnnotationGlobalServerInterceptorConfigurer.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/interceptor/AnnotationGlobalServerInterceptorConfigurer.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/interceptor/AnnotationGlobalServerInterceptorConfigurer.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/interceptor/AnnotationGlobalServerInterceptorConfigurer.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/interceptor/GlobalServerInterceptorConfigurer.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/interceptor/GlobalServerInterceptorConfigurer.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/interceptor/GlobalServerInterceptorConfigurer.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/interceptor/GlobalServerInterceptorConfigurer.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/interceptor/GlobalServerInterceptorRegistry.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/interceptor/GlobalServerInterceptorRegistry.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/interceptor/GlobalServerInterceptorRegistry.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/interceptor/GlobalServerInterceptorRegistry.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/interceptor/GrpcGlobalServerInterceptor.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/interceptor/GrpcGlobalServerInterceptor.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/interceptor/GrpcGlobalServerInterceptor.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/interceptor/GrpcGlobalServerInterceptor.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/interceptor/OrderedServerInterceptor.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/interceptor/OrderedServerInterceptor.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/interceptor/OrderedServerInterceptor.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/interceptor/OrderedServerInterceptor.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/interceptor/package-info.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/interceptor/package-info.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/interceptor/package-info.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/interceptor/package-info.java
diff --git a/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/metrics/MetricsServerInstruments.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/metrics/MetricsServerInstruments.java
new file mode 100644
index 000000000..0fb4319b0
--- /dev/null
+++ b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/metrics/MetricsServerInstruments.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (c) 2016-2023 The gRPC-Spring 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 net.devh.boot.grpc.server.metrics;
+
+import java.time.Duration;
+
+import io.micrometer.core.instrument.Counter;
+import io.micrometer.core.instrument.DistributionSummary;
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.Timer;
+import io.micrometer.core.instrument.binder.BaseUnits;
+
+/*
+ * The instruments used to record metrics on server.
+ */
+public final class MetricsServerInstruments {
+
+ private MetricsServerInstruments() {}
+
+ /*
+ * Server side metrics defined in gRFC A66. Please note that these are the
+ * names used for instrumentation and can be changed by exporters in an unpredictable manner depending on the
+ * destination.
+ */
+ private static final String SERVER_CALL_STARTED = "grpc.server.call.started";
+ private static final String SERVER_SENT_COMPRESSED_MESSAGE_SIZE =
+ "grpc.server.call.sent_total_compressed_message_size";
+ private static final String SERVER_RECEIVED_COMPRESSED_MESSAGE_SIZE =
+ "grpc.server.call.rcvd_total_compressed_message_size";
+ private static final String SERVER_CALL_DURATION =
+ "grpc.server.call.duration";
+ private static final double[] DEFAULT_SIZE_BUCKETS =
+ new double[] {1024d, 2048d, 4096d, 16384d, 65536d, 262144d, 1048576d,
+ 4194304d, 16777216d, 67108864d, 268435456d, 1073741824d, 4294967296d};
+ private static final Duration[] DEFAULT_LATENCY_BUCKETS =
+ new Duration[] {Duration.ofNanos(10000), Duration.ofNanos(50000), Duration.ofNanos(100000),
+ Duration.ofNanos(300000), Duration.ofNanos(600000), Duration.ofNanos(800000),
+ Duration.ofMillis(1), Duration.ofMillis(2), Duration.ofMillis(3), Duration.ofMillis(4),
+ Duration.ofMillis(5), Duration.ofMillis(6), Duration.ofMillis(8), Duration.ofMillis(10),
+ Duration.ofMillis(13), Duration.ofMillis(16), Duration.ofMillis(20), Duration.ofMillis(25),
+ Duration.ofMillis(30), Duration.ofMillis(40), Duration.ofMillis(50), Duration.ofMillis(65),
+ Duration.ofMillis(80), Duration.ofMillis(100), Duration.ofMillis(130), Duration.ofMillis(160),
+ Duration.ofMillis(200), Duration.ofMillis(250), Duration.ofMillis(300), Duration.ofMillis(400),
+ Duration.ofMillis(500), Duration.ofMillis(650), Duration.ofMillis(800),
+ Duration.ofSeconds(1), Duration.ofSeconds(2), Duration.ofSeconds(5), Duration.ofSeconds(10),
+ Duration.ofSeconds(20), Duration.ofSeconds(50), Duration.ofSeconds(100)};
+
+ static MetricsServerMeters newServerMetricsMeters(MeterRegistry registry) {
+ MetricsServerMeters.Builder builder = MetricsServerMeters.newBuilder();
+
+ builder.setServerCallCounter(Counter.builder(SERVER_CALL_STARTED)
+ .description("The total number of RPC attempts started from the server side, including "
+ + "those that have not completed.")
+ .withRegistry(registry));
+
+ builder.setSentMessageSizeDistribution(DistributionSummary.builder(
+ SERVER_SENT_COMPRESSED_MESSAGE_SIZE)
+ .description("Compressed message bytes sent per server call")
+ .baseUnit(BaseUnits.BYTES)
+ .serviceLevelObjectives(DEFAULT_SIZE_BUCKETS)
+ .withRegistry(registry));
+
+ builder.setReceivedMessageSizeDistribution(DistributionSummary.builder(
+ SERVER_RECEIVED_COMPRESSED_MESSAGE_SIZE)
+ .description("Compressed message bytes received per server call")
+ .baseUnit(BaseUnits.BYTES)
+ .serviceLevelObjectives(DEFAULT_SIZE_BUCKETS)
+ .withRegistry(registry));
+
+ builder.setServerCallDuration(Timer.builder(SERVER_CALL_DURATION)
+ .description("Time taken to complete a call from server transport's perspective")
+ .serviceLevelObjectives(DEFAULT_LATENCY_BUCKETS)
+ .withRegistry(registry));
+
+ return builder.build();
+ }
+}
diff --git a/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/metrics/MetricsServerMeters.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/metrics/MetricsServerMeters.java
new file mode 100644
index 000000000..7ae778847
--- /dev/null
+++ b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/metrics/MetricsServerMeters.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (c) 2016-2023 The gRPC-Spring 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 net.devh.boot.grpc.server.metrics;
+
+import io.micrometer.core.instrument.Counter;
+import io.micrometer.core.instrument.DistributionSummary;
+import io.micrometer.core.instrument.Meter.MeterProvider;
+import io.micrometer.core.instrument.Timer;
+
+/*
+ * Collection of server metrics meters.
+ */
+public class MetricsServerMeters {
+
+ private MeterProvider serverCallCounter;
+ private MeterProvider sentMessageSizeDistribution;
+ private MeterProvider receivedMessageSizeDistribution;
+ private MeterProvider serverCallDuration;
+
+ private MetricsServerMeters(Builder builder) {
+ this.serverCallCounter = builder.serverCallCounter;
+ this.sentMessageSizeDistribution = builder.sentMessageSizeDistribution;
+ this.receivedMessageSizeDistribution = builder.receivedMessageSizeDistribution;
+ this.serverCallDuration = builder.serverCallDuration;
+ }
+
+ public MeterProvider getServerCallCounter() {
+ return this.serverCallCounter;
+ }
+
+ public MeterProvider getSentMessageSizeDistribution() {
+ return this.sentMessageSizeDistribution;
+ }
+
+ public MeterProvider getReceivedMessageSizeDistribution() {
+ return this.receivedMessageSizeDistribution;
+ }
+
+ public MeterProvider getServerCallDuration() {
+ return this.serverCallDuration;
+ }
+
+ public static Builder newBuilder() {
+ return new Builder();
+ }
+
+ static class Builder {
+
+ private MeterProvider serverCallCounter;
+ private MeterProvider sentMessageSizeDistribution;
+ private MeterProvider receivedMessageSizeDistribution;
+ private MeterProvider serverCallDuration;
+
+ private Builder() {}
+
+ public Builder setServerCallCounter(MeterProvider counter) {
+ this.serverCallCounter = counter;
+ return this;
+ }
+
+ public Builder setSentMessageSizeDistribution(MeterProvider distribution) {
+ this.sentMessageSizeDistribution = distribution;
+ return this;
+ }
+
+ public Builder setReceivedMessageSizeDistribution(MeterProvider distribution) {
+ this.receivedMessageSizeDistribution = distribution;
+ return this;
+ }
+
+ public Builder setServerCallDuration(MeterProvider timer) {
+ this.serverCallDuration = timer;
+ return this;
+ }
+
+ public MetricsServerMeters build() {
+ return new MetricsServerMeters(this);
+ }
+ }
+}
diff --git a/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/metrics/MetricsServerStreamTracers.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/metrics/MetricsServerStreamTracers.java
new file mode 100644
index 000000000..f7eba5b9c
--- /dev/null
+++ b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/metrics/MetricsServerStreamTracers.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (c) 2016-2023 The gRPC-Spring 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 net.devh.boot.grpc.server.metrics;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
+import java.util.concurrent.atomic.AtomicLongFieldUpdater;
+import java.util.function.Supplier;
+
+import com.google.common.base.Stopwatch;
+
+import io.grpc.Metadata;
+import io.grpc.ServerStreamTracer;
+import io.grpc.Status;
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.Tags;
+import net.devh.boot.grpc.common.util.Constants;
+
+/**
+ * Provides factories for {@link io.grpc.StreamTracer} that records metrics.
+ *
+ *
+ * On the server-side, there is only one ServerStream per each ServerCall, and ServerStream starts earlier than the
+ * ServerCall. Therefore, only one tracer is created per stream/call and it's the tracer that reports the metrics
+ * summary.
+ *
+ * Note: This class uses experimental grpc-java-API features.
+ */
+public final class MetricsServerStreamTracers {
+
+ private static final Supplier STOPWATCH_SUPPLIER = Stopwatch::createUnstarted;
+ private final Supplier stopwatchSupplier;
+ private static final String INSTRUMENTATION_SOURCE_TAG_KEY = "instrumentation_source";
+ private static final String INSTRUMENTATION_VERSION_TAG_KEY = "instrumentation_version";
+
+ public MetricsServerStreamTracers() {
+ this(STOPWATCH_SUPPLIER);
+ }
+
+ public MetricsServerStreamTracers(Supplier stopwatchSupplier) {
+ this.stopwatchSupplier = checkNotNull(stopwatchSupplier, "stopwatchSupplier");
+ }
+
+ /**
+ * Returns a {@link io.grpc.ServerStreamTracer.Factory} with default metrics definitions.
+ *
+ * @param registry The MeterRegistry used to create the metrics.
+ */
+ public ServerStreamTracer.Factory getMetricsServerTracerFactory(MeterRegistry registry) {
+ return new MetricsServerTracerFactory(registry);
+ }
+
+ /**
+ * Returns a {@link io.grpc.ServerStreamTracer.Factory} with metrics definitions from custom
+ * {@link MetricsServerMeters}.
+ *
+ * @param meters The MetricsServerMeters used to configure the metrics definitions.
+ */
+ public ServerStreamTracer.Factory getMetricsServerTracerFactory(MetricsServerMeters meters) {
+ return new MetricsServerTracerFactory(meters);
+ }
+
+ private static final class ServerTracer extends ServerStreamTracer {
+ private final MetricsServerStreamTracers tracer;
+ private final String fullMethodName;
+ private final MetricsServerMeters metricsServerMeters;
+ private final Stopwatch stopwatch;
+ private static final AtomicLongFieldUpdater outboundWireSizeUpdater =
+ AtomicLongFieldUpdater.newUpdater(ServerTracer.class, "outboundWireSize");
+ private static final AtomicLongFieldUpdater inboundWireSizeUpdater =
+ AtomicLongFieldUpdater.newUpdater(ServerTracer.class, "inboundWireSize");
+ private static final AtomicIntegerFieldUpdater streamClosedUpdater =
+ AtomicIntegerFieldUpdater.newUpdater(ServerTracer.class, "streamClosed");
+ private volatile long outboundWireSize;
+ private volatile long inboundWireSize;
+ private volatile int streamClosed;
+
+
+ ServerTracer(MetricsServerStreamTracers tracer, String fullMethodName, MetricsServerMeters meters) {
+ this.tracer = checkNotNull(tracer, "tracer");
+ this.fullMethodName = fullMethodName;
+ this.metricsServerMeters = meters;
+ // start stopwatch
+ this.stopwatch = tracer.stopwatchSupplier.get().start();
+ }
+
+ @Override
+ public void serverCallStarted(ServerCallInfo, ?> callInfo) {
+ this.metricsServerMeters.getServerCallCounter()
+ .withTags(Tags.of("grpc.method", this.fullMethodName,
+ INSTRUMENTATION_SOURCE_TAG_KEY, Constants.LIBRARY_NAME,
+ INSTRUMENTATION_VERSION_TAG_KEY, Constants.VERSION))
+ .increment();
+ }
+
+ @Override
+ public void outboundWireSize(long bytes) {
+ outboundWireSizeUpdater.getAndAdd(this, bytes);
+ }
+
+ @Override
+ public void inboundWireSize(long bytes) {
+ inboundWireSizeUpdater.getAndAdd(this, bytes);
+ }
+
+ @Override
+ public void streamClosed(Status status) {
+ if (streamClosedUpdater.getAndSet(this, 1) != 0) {
+ return;
+ }
+ long callLatencyNanos = stopwatch.elapsed(TimeUnit.NANOSECONDS);
+
+ Tags serverMetricTags =
+ Tags.of("grpc.method", this.fullMethodName,
+ "grpc.status", status.getCode().toString(),
+ INSTRUMENTATION_SOURCE_TAG_KEY, Constants.LIBRARY_NAME,
+ INSTRUMENTATION_VERSION_TAG_KEY, Constants.VERSION);
+ this.metricsServerMeters.getServerCallDuration()
+ .withTags(serverMetricTags)
+ .record(callLatencyNanos, TimeUnit.NANOSECONDS);
+ this.metricsServerMeters.getSentMessageSizeDistribution()
+ .withTags(serverMetricTags)
+ .record(outboundWireSize);
+ this.metricsServerMeters.getReceivedMessageSizeDistribution()
+ .withTags(serverMetricTags)
+ .record(inboundWireSize);
+ }
+ }
+
+ final class MetricsServerTracerFactory extends ServerStreamTracer.Factory {
+
+ private final MetricsServerMeters metricsServerMeters;
+
+ MetricsServerTracerFactory(MeterRegistry registry) {
+ this(MetricsServerInstruments.newServerMetricsMeters(registry));
+ }
+
+ MetricsServerTracerFactory(MetricsServerMeters metricsServerMeters) {
+ this.metricsServerMeters = metricsServerMeters;
+ }
+
+ @Override
+ public ServerStreamTracer newServerStreamTracer(String fullMethodName, Metadata headers) {
+ return new ServerTracer(MetricsServerStreamTracers.this, fullMethodName, this.metricsServerMeters);
+ }
+ }
+
+}
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/nameresolver/SelfNameResolver.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/nameresolver/SelfNameResolver.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/nameresolver/SelfNameResolver.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/nameresolver/SelfNameResolver.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/nameresolver/SelfNameResolverFactory.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/nameresolver/SelfNameResolverFactory.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/nameresolver/SelfNameResolverFactory.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/nameresolver/SelfNameResolverFactory.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/nameresolver/package-info.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/nameresolver/package-info.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/nameresolver/package-info.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/nameresolver/package-info.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/scope/GrpcRequestScope.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/scope/GrpcRequestScope.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/scope/GrpcRequestScope.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/scope/GrpcRequestScope.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/authentication/AnonymousAuthenticationReader.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/authentication/AnonymousAuthenticationReader.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/authentication/AnonymousAuthenticationReader.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/authentication/AnonymousAuthenticationReader.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/authentication/BasicGrpcAuthenticationReader.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/authentication/BasicGrpcAuthenticationReader.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/authentication/BasicGrpcAuthenticationReader.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/authentication/BasicGrpcAuthenticationReader.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/authentication/BearerAuthenticationReader.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/authentication/BearerAuthenticationReader.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/authentication/BearerAuthenticationReader.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/authentication/BearerAuthenticationReader.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/authentication/CompositeGrpcAuthenticationReader.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/authentication/CompositeGrpcAuthenticationReader.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/authentication/CompositeGrpcAuthenticationReader.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/authentication/CompositeGrpcAuthenticationReader.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/authentication/GrpcAuthenticationReader.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/authentication/GrpcAuthenticationReader.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/authentication/GrpcAuthenticationReader.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/authentication/GrpcAuthenticationReader.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/authentication/SSLContextGrpcAuthenticationReader.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/authentication/SSLContextGrpcAuthenticationReader.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/authentication/SSLContextGrpcAuthenticationReader.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/authentication/SSLContextGrpcAuthenticationReader.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/authentication/X509CertificateAuthentication.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/authentication/X509CertificateAuthentication.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/authentication/X509CertificateAuthentication.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/authentication/X509CertificateAuthentication.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/authentication/X509CertificateAuthenticationProvider.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/authentication/X509CertificateAuthenticationProvider.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/authentication/X509CertificateAuthenticationProvider.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/authentication/X509CertificateAuthenticationProvider.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/authentication/package-info.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/authentication/package-info.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/authentication/package-info.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/authentication/package-info.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/check/AbstractGrpcSecurityMetadataSource.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/check/AbstractGrpcSecurityMetadataSource.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/check/AbstractGrpcSecurityMetadataSource.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/check/AbstractGrpcSecurityMetadataSource.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/check/AccessPredicate.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/check/AccessPredicate.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/check/AccessPredicate.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/check/AccessPredicate.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/check/AccessPredicateConfigAttribute.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/check/AccessPredicateConfigAttribute.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/check/AccessPredicateConfigAttribute.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/check/AccessPredicateConfigAttribute.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/check/AccessPredicateVoter.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/check/AccessPredicateVoter.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/check/AccessPredicateVoter.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/check/AccessPredicateVoter.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/check/AccessPredicates.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/check/AccessPredicates.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/check/AccessPredicates.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/check/AccessPredicates.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/check/GrpcSecurityMetadataSource.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/check/GrpcSecurityMetadataSource.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/check/GrpcSecurityMetadataSource.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/check/GrpcSecurityMetadataSource.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/check/ManualGrpcSecurityMetadataSource.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/check/ManualGrpcSecurityMetadataSource.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/check/ManualGrpcSecurityMetadataSource.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/check/ManualGrpcSecurityMetadataSource.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/check/package-info.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/check/package-info.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/check/package-info.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/check/package-info.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/AbstractAuthenticatingServerCallListener.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/interceptors/AbstractAuthenticatingServerCallListener.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/AbstractAuthenticatingServerCallListener.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/interceptors/AbstractAuthenticatingServerCallListener.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/AuthenticatingServerInterceptor.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/interceptors/AuthenticatingServerInterceptor.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/AuthenticatingServerInterceptor.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/interceptors/AuthenticatingServerInterceptor.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/AuthorizationCheckingServerInterceptor.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/interceptors/AuthorizationCheckingServerInterceptor.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/AuthorizationCheckingServerInterceptor.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/interceptors/AuthorizationCheckingServerInterceptor.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/DefaultAuthenticatingServerInterceptor.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/interceptors/DefaultAuthenticatingServerInterceptor.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/DefaultAuthenticatingServerInterceptor.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/interceptors/DefaultAuthenticatingServerInterceptor.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/ExceptionTranslatingServerInterceptor.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/interceptors/ExceptionTranslatingServerInterceptor.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/ExceptionTranslatingServerInterceptor.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/interceptors/ExceptionTranslatingServerInterceptor.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/package-info.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/interceptors/package-info.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/interceptors/package-info.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/interceptors/package-info.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/package-info.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/package-info.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/security/package-info.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/security/package-info.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/serverfactory/AbstractGrpcServerFactory.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/serverfactory/AbstractGrpcServerFactory.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/serverfactory/AbstractGrpcServerFactory.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/serverfactory/AbstractGrpcServerFactory.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/serverfactory/GrpcServerConfigurer.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/serverfactory/GrpcServerConfigurer.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/serverfactory/GrpcServerConfigurer.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/serverfactory/GrpcServerConfigurer.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/serverfactory/GrpcServerFactory.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/serverfactory/GrpcServerFactory.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/serverfactory/GrpcServerFactory.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/serverfactory/GrpcServerFactory.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/serverfactory/GrpcServerLifecycle.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/serverfactory/GrpcServerLifecycle.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/serverfactory/GrpcServerLifecycle.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/serverfactory/GrpcServerLifecycle.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/serverfactory/InProcessGrpcServerFactory.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/serverfactory/InProcessGrpcServerFactory.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/serverfactory/InProcessGrpcServerFactory.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/serverfactory/InProcessGrpcServerFactory.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/serverfactory/NettyGrpcServerFactory.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/serverfactory/NettyGrpcServerFactory.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/serverfactory/NettyGrpcServerFactory.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/serverfactory/NettyGrpcServerFactory.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/serverfactory/ShadedNettyGrpcServerFactory.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/serverfactory/ShadedNettyGrpcServerFactory.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/serverfactory/ShadedNettyGrpcServerFactory.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/serverfactory/ShadedNettyGrpcServerFactory.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/serverfactory/package-info.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/serverfactory/package-info.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/serverfactory/package-info.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/serverfactory/package-info.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/service/AnnotationGrpcServiceDiscoverer.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/service/AnnotationGrpcServiceDiscoverer.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/service/AnnotationGrpcServiceDiscoverer.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/service/AnnotationGrpcServiceDiscoverer.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/service/GrpcService.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/service/GrpcService.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/service/GrpcService.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/service/GrpcService.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/service/GrpcServiceDefinition.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/service/GrpcServiceDefinition.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/service/GrpcServiceDefinition.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/service/GrpcServiceDefinition.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/service/GrpcServiceDiscoverer.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/service/GrpcServiceDiscoverer.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/service/GrpcServiceDiscoverer.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/service/GrpcServiceDiscoverer.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/service/package-info.java b/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/service/package-info.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/service/package-info.java
rename to grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/service/package-info.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/grpc-server-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
rename to grpc-server-spring-boot-starter/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
diff --git a/grpc-server-spring-boot-autoconfigure/src/test/java/net/devh/boot/grpc/server/advice/GrpcAdviceDiscovererTest.java b/grpc-server-spring-boot-starter/src/test/java/net/devh/boot/grpc/server/advice/GrpcAdviceDiscovererTest.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/test/java/net/devh/boot/grpc/server/advice/GrpcAdviceDiscovererTest.java
rename to grpc-server-spring-boot-starter/src/test/java/net/devh/boot/grpc/server/advice/GrpcAdviceDiscovererTest.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/test/java/net/devh/boot/grpc/server/autoconfigure/AwaitableStreamObserver.java b/grpc-server-spring-boot-starter/src/test/java/net/devh/boot/grpc/server/autoconfigure/AwaitableStreamObserver.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/test/java/net/devh/boot/grpc/server/autoconfigure/AwaitableStreamObserver.java
rename to grpc-server-spring-boot-starter/src/test/java/net/devh/boot/grpc/server/autoconfigure/AwaitableStreamObserver.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/test/java/net/devh/boot/grpc/server/autoconfigure/GrpcHealthServiceDefaultAutoConfigurationTest.java b/grpc-server-spring-boot-starter/src/test/java/net/devh/boot/grpc/server/autoconfigure/GrpcHealthServiceDefaultAutoConfigurationTest.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/test/java/net/devh/boot/grpc/server/autoconfigure/GrpcHealthServiceDefaultAutoConfigurationTest.java
rename to grpc-server-spring-boot-starter/src/test/java/net/devh/boot/grpc/server/autoconfigure/GrpcHealthServiceDefaultAutoConfigurationTest.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/test/java/net/devh/boot/grpc/server/autoconfigure/GrpcHealthServiceFalseAutoConfigurationTest.java b/grpc-server-spring-boot-starter/src/test/java/net/devh/boot/grpc/server/autoconfigure/GrpcHealthServiceFalseAutoConfigurationTest.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/test/java/net/devh/boot/grpc/server/autoconfigure/GrpcHealthServiceFalseAutoConfigurationTest.java
rename to grpc-server-spring-boot-starter/src/test/java/net/devh/boot/grpc/server/autoconfigure/GrpcHealthServiceFalseAutoConfigurationTest.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/test/java/net/devh/boot/grpc/server/autoconfigure/GrpcHealthServiceTrueAutoConfigurationTest.java b/grpc-server-spring-boot-starter/src/test/java/net/devh/boot/grpc/server/autoconfigure/GrpcHealthServiceTrueAutoConfigurationTest.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/test/java/net/devh/boot/grpc/server/autoconfigure/GrpcHealthServiceTrueAutoConfigurationTest.java
rename to grpc-server-spring-boot-starter/src/test/java/net/devh/boot/grpc/server/autoconfigure/GrpcHealthServiceTrueAutoConfigurationTest.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/test/java/net/devh/boot/grpc/server/autoconfigure/GrpcReflectionServiceDefaultAutoConfigurationTest.java b/grpc-server-spring-boot-starter/src/test/java/net/devh/boot/grpc/server/autoconfigure/GrpcReflectionServiceDefaultAutoConfigurationTest.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/test/java/net/devh/boot/grpc/server/autoconfigure/GrpcReflectionServiceDefaultAutoConfigurationTest.java
rename to grpc-server-spring-boot-starter/src/test/java/net/devh/boot/grpc/server/autoconfigure/GrpcReflectionServiceDefaultAutoConfigurationTest.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/test/java/net/devh/boot/grpc/server/autoconfigure/GrpcReflectionServiceFalseAutoConfigurationTest.java b/grpc-server-spring-boot-starter/src/test/java/net/devh/boot/grpc/server/autoconfigure/GrpcReflectionServiceFalseAutoConfigurationTest.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/test/java/net/devh/boot/grpc/server/autoconfigure/GrpcReflectionServiceFalseAutoConfigurationTest.java
rename to grpc-server-spring-boot-starter/src/test/java/net/devh/boot/grpc/server/autoconfigure/GrpcReflectionServiceFalseAutoConfigurationTest.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/test/java/net/devh/boot/grpc/server/autoconfigure/GrpcReflectionServiceTrueAutoConfigurationTest.java b/grpc-server-spring-boot-starter/src/test/java/net/devh/boot/grpc/server/autoconfigure/GrpcReflectionServiceTrueAutoConfigurationTest.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/test/java/net/devh/boot/grpc/server/autoconfigure/GrpcReflectionServiceTrueAutoConfigurationTest.java
rename to grpc-server-spring-boot-starter/src/test/java/net/devh/boot/grpc/server/autoconfigure/GrpcReflectionServiceTrueAutoConfigurationTest.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/test/java/net/devh/boot/grpc/server/config/GrpcServerPropertiesConfig.java b/grpc-server-spring-boot-starter/src/test/java/net/devh/boot/grpc/server/config/GrpcServerPropertiesConfig.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/test/java/net/devh/boot/grpc/server/config/GrpcServerPropertiesConfig.java
rename to grpc-server-spring-boot-starter/src/test/java/net/devh/boot/grpc/server/config/GrpcServerPropertiesConfig.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/test/java/net/devh/boot/grpc/server/config/GrpcServerPropertiesGivenUnitTest.java b/grpc-server-spring-boot-starter/src/test/java/net/devh/boot/grpc/server/config/GrpcServerPropertiesGivenUnitTest.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/test/java/net/devh/boot/grpc/server/config/GrpcServerPropertiesGivenUnitTest.java
rename to grpc-server-spring-boot-starter/src/test/java/net/devh/boot/grpc/server/config/GrpcServerPropertiesGivenUnitTest.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/test/java/net/devh/boot/grpc/server/config/GrpcServerPropertiesNegativeGivenUnitTest.java b/grpc-server-spring-boot-starter/src/test/java/net/devh/boot/grpc/server/config/GrpcServerPropertiesNegativeGivenUnitTest.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/test/java/net/devh/boot/grpc/server/config/GrpcServerPropertiesNegativeGivenUnitTest.java
rename to grpc-server-spring-boot-starter/src/test/java/net/devh/boot/grpc/server/config/GrpcServerPropertiesNegativeGivenUnitTest.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/test/java/net/devh/boot/grpc/server/config/GrpcServerPropertiesNegativeNoUnitTest.java b/grpc-server-spring-boot-starter/src/test/java/net/devh/boot/grpc/server/config/GrpcServerPropertiesNegativeNoUnitTest.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/test/java/net/devh/boot/grpc/server/config/GrpcServerPropertiesNegativeNoUnitTest.java
rename to grpc-server-spring-boot-starter/src/test/java/net/devh/boot/grpc/server/config/GrpcServerPropertiesNegativeNoUnitTest.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/test/java/net/devh/boot/grpc/server/config/GrpcServerPropertiesNoUnitTest.java b/grpc-server-spring-boot-starter/src/test/java/net/devh/boot/grpc/server/config/GrpcServerPropertiesNoUnitTest.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/test/java/net/devh/boot/grpc/server/config/GrpcServerPropertiesNoUnitTest.java
rename to grpc-server-spring-boot-starter/src/test/java/net/devh/boot/grpc/server/config/GrpcServerPropertiesNoUnitTest.java
diff --git a/grpc-server-spring-boot-starter/src/test/java/net/devh/boot/grpc/server/metrics/FakeClock.java b/grpc-server-spring-boot-starter/src/test/java/net/devh/boot/grpc/server/metrics/FakeClock.java
new file mode 100644
index 000000000..43cfd2ac5
--- /dev/null
+++ b/grpc-server-spring-boot-starter/src/test/java/net/devh/boot/grpc/server/metrics/FakeClock.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2016-2023 The gRPC-Spring 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 net.devh.boot.grpc.server.metrics;
+
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+
+import com.google.common.base.Stopwatch;
+import com.google.common.base.Ticker;
+
+/**
+ * A manipulated clock that exports a {@link com.google.common.base.Ticker}.
+ */
+public final class FakeClock {
+ private long currentTimeNanos;
+ private final Ticker ticker =
+ new Ticker() {
+ @Override
+ public long read() {
+ return currentTimeNanos;
+ }
+ };
+
+ private final Supplier stopwatchSupplier = () -> Stopwatch.createUnstarted(ticker);
+
+ /**
+ * Forward the time by the given duration.
+ */
+ public void forwardTime(long value, TimeUnit unit) {
+ currentTimeNanos += unit.toNanos(value);
+ }
+
+ /**
+ * Provides a stopwatch instance that uses the fake clock ticker.
+ */
+ public Supplier getStopwatchSupplier() {
+ return stopwatchSupplier;
+ }
+}
diff --git a/grpc-server-spring-boot-starter/src/test/java/net/devh/boot/grpc/server/metrics/MetricsServerStreamTracersTest.java b/grpc-server-spring-boot-starter/src/test/java/net/devh/boot/grpc/server/metrics/MetricsServerStreamTracersTest.java
new file mode 100644
index 000000000..2b6d85bfc
--- /dev/null
+++ b/grpc-server-spring-boot-starter/src/test/java/net/devh/boot/grpc/server/metrics/MetricsServerStreamTracersTest.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (c) 2016-2023 The gRPC-Spring 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 net.devh.boot.grpc.server.metrics;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.io.InputStream;
+
+import javax.annotation.Nullable;
+
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import io.grpc.Attributes;
+import io.grpc.Metadata;
+import io.grpc.MethodDescriptor;
+import io.grpc.ServerStreamTracer;
+import io.grpc.ServerStreamTracer.ServerCallInfo;
+import io.grpc.Status;
+import io.micrometer.core.instrument.MeterRegistry;
+import io.micrometer.core.instrument.Metrics;
+import io.micrometer.core.instrument.distribution.CountAtBucket;
+import io.micrometer.core.instrument.distribution.HistogramSnapshot;
+import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
+import net.devh.boot.grpc.common.util.Constants;
+
+/**
+ * Tests for {@link MetricsServerStreamTracers}.
+ */
+class MetricsServerStreamTracersTest {
+
+ private static final String SERVER_CALL_STARTED = "grpc.server.call.started";
+ private static final String SERVER_SENT_COMPRESSED_MESSAGE_SIZE =
+ "grpc.server.call.sent_total_compressed_message_size";
+ private static final String SERVER_RECEIVED_COMPRESSED_MESSAGE_SIZE =
+ "grpc.server.call.rcvd_total_compressed_message_size";
+ private static final String SERVER_CALL_DURATION =
+ "grpc.server.call.duration";
+ private static final String FULL_METHOD_NAME = "package1.service1/method1";
+ private static final String GRPC_METHOD_TAG_KEY = "grpc.method";
+ private static final String GRPC_STATUS_TAG_KEY = "grpc.status";
+ private static final String INSTRUMENTATION_SOURCE_TAG_KEY = "instrumentation_source";
+ private static final String INSTRUMENTATION_SOURCE_TAG_VALUE = Constants.LIBRARY_NAME;
+ private static final String INSTRUMENTATION_VERSION_TAG_KEY = "instrumentation_version";
+ private static final String INSTRUMENTATION_VERSION_TAG_VALUE = Constants.VERSION;
+
+
+ private static class StringInputStream extends InputStream {
+ final String string;
+
+ StringInputStream(String string) {
+ this.string = string;
+ }
+
+ @Override
+ public int read() {
+ // InProcessTransport doesn't actually read bytes from the InputStream. The InputStream is
+ // passed to the InProcess server and consumed by MARSHALLER.parse().
+ throw new UnsupportedOperationException("Should not be called");
+ }
+ }
+
+ static class CallInfo extends ServerCallInfo {
+ private final MethodDescriptor methodDescriptor;
+ private final Attributes attributes;
+ private final String authority;
+
+ CallInfo(
+ MethodDescriptor methodDescriptor,
+ Attributes attributes,
+ @Nullable String authority) {
+ this.methodDescriptor = methodDescriptor;
+ this.attributes = attributes;
+ this.authority = authority;
+ }
+
+ @Override
+ public MethodDescriptor getMethodDescriptor() {
+ return methodDescriptor;
+ }
+
+ @Override
+ public Attributes getAttributes() {
+ return attributes;
+ }
+
+ @Nullable
+ @Override
+ public String getAuthority() {
+ return authority;
+ }
+ }
+
+ private static final MethodDescriptor.Marshaller MARSHALLER =
+ new MethodDescriptor.Marshaller() {
+ @Override
+ public InputStream stream(String value) {
+ return new StringInputStream(value);
+ }
+
+ @Override
+ public String parse(InputStream stream) {
+ return ((StringInputStream) stream).string;
+ }
+ };
+ private final MethodDescriptor method =
+ MethodDescriptor.newBuilder()
+ .setType(MethodDescriptor.MethodType.UNKNOWN)
+ .setRequestMarshaller(MARSHALLER)
+ .setResponseMarshaller(MARSHALLER)
+ .setFullMethodName(FULL_METHOD_NAME)
+ .build();
+
+ private FakeClock fakeClock;
+ private MeterRegistry meterRegistry;
+
+ @BeforeEach
+ void setUp() {
+ fakeClock = new FakeClock();
+ meterRegistry = new SimpleMeterRegistry();
+ Metrics.globalRegistry.add(meterRegistry);
+ }
+
+ @AfterEach
+ void tearDown() {
+ meterRegistry.clear();
+ Metrics.globalRegistry.clear();
+ }
+
+ @Test
+ void serverBasicMetrics() {
+ MetricsServerStreamTracers localServerStreamTracers =
+ new MetricsServerStreamTracers(fakeClock.getStopwatchSupplier());
+ ServerStreamTracer.Factory tracerFactory =
+ localServerStreamTracers.getMetricsServerTracerFactory(meterRegistry);
+ ServerStreamTracer tracer = tracerFactory.newServerStreamTracer(method.getFullMethodName(), new Metadata());
+ tracer.serverCallStarted(
+ new CallInfo<>(method, Attributes.EMPTY, null));
+
+ assertThat(meterRegistry.get(SERVER_CALL_STARTED)
+ .tag(GRPC_METHOD_TAG_KEY, FULL_METHOD_NAME)
+ .tag(INSTRUMENTATION_SOURCE_TAG_KEY, INSTRUMENTATION_SOURCE_TAG_VALUE)
+ .tag(INSTRUMENTATION_VERSION_TAG_KEY, INSTRUMENTATION_VERSION_TAG_VALUE)
+ .counter()
+ .count()).isEqualTo(1);
+
+ tracer.inboundWireSize(34);
+
+ fakeClock.forwardTime(26, MILLISECONDS);
+
+ tracer.outboundWireSize(1028);
+
+ tracer.inboundWireSize(154);
+
+ tracer.outboundWireSize(99);
+
+ fakeClock.forwardTime(14, MILLISECONDS);
+
+ tracer.streamClosed(Status.CANCELLED);
+
+ HistogramSnapshot sentMessageSizeSnapShot = meterRegistry.get(SERVER_SENT_COMPRESSED_MESSAGE_SIZE)
+ .tag(GRPC_METHOD_TAG_KEY, FULL_METHOD_NAME)
+ .tag(GRPC_STATUS_TAG_KEY, Status.Code.CANCELLED.toString())
+ .tag(INSTRUMENTATION_SOURCE_TAG_KEY, INSTRUMENTATION_SOURCE_TAG_VALUE)
+ .tag(INSTRUMENTATION_VERSION_TAG_KEY, INSTRUMENTATION_VERSION_TAG_VALUE)
+ .summary()
+ .takeSnapshot();
+ HistogramSnapshot expectedSentMessageSizeHistogram = HistogramSnapshot.empty(1L, 1127L, 1127L);
+ assertThat(sentMessageSizeSnapShot.count()).isEqualTo(expectedSentMessageSizeHistogram.count());
+ assertThat(sentMessageSizeSnapShot.total()).isEqualTo(expectedSentMessageSizeHistogram.total());
+ assertThat(sentMessageSizeSnapShot.histogramCounts()).contains(new CountAtBucket(2048.0, 1));
+
+ HistogramSnapshot receivedMessageSizeSnapShot =
+ meterRegistry.get(SERVER_RECEIVED_COMPRESSED_MESSAGE_SIZE)
+ .tag(GRPC_METHOD_TAG_KEY, FULL_METHOD_NAME)
+ .tag(GRPC_STATUS_TAG_KEY, Status.Code.CANCELLED.toString())
+ .tag(INSTRUMENTATION_SOURCE_TAG_KEY, INSTRUMENTATION_SOURCE_TAG_VALUE)
+ .tag(INSTRUMENTATION_VERSION_TAG_KEY, INSTRUMENTATION_VERSION_TAG_VALUE)
+ .summary()
+ .takeSnapshot();
+ HistogramSnapshot expectedReceivedMessageSizeHistogram = HistogramSnapshot.empty(1L, 188L, 188L);
+ assertThat(receivedMessageSizeSnapShot.count()).isEqualTo(expectedReceivedMessageSizeHistogram.count());
+ assertThat(receivedMessageSizeSnapShot.total()).isEqualTo(expectedReceivedMessageSizeHistogram.total());
+ assertThat(receivedMessageSizeSnapShot.histogramCounts()).contains(new CountAtBucket(1024.0, 1));
+ // TODO(dnvindhya) : Figure out a way to generate normal histogram instead of cumulative histogram
+ // with fixed buckets
+ /*
+ * assertThat(receivedMessageSizeSnapShot.histogramCounts()).contains(new CountAtBucket(1024.0, 1), new
+ * CountAtBucket(2048.0, 0));
+ */
+
+ HistogramSnapshot callDurationSnapshot = meterRegistry.get(SERVER_CALL_DURATION)
+ .tag(GRPC_METHOD_TAG_KEY, FULL_METHOD_NAME)
+ .tag(GRPC_STATUS_TAG_KEY, Status.Code.CANCELLED.toString())
+ .tag(INSTRUMENTATION_SOURCE_TAG_KEY, INSTRUMENTATION_SOURCE_TAG_VALUE)
+ .tag(INSTRUMENTATION_VERSION_TAG_KEY, INSTRUMENTATION_VERSION_TAG_VALUE)
+ .timer()
+ .takeSnapshot();
+ HistogramSnapshot expectedCallDurationHistogram = HistogramSnapshot.empty(1L, 40L, 40);
+ assertThat(callDurationSnapshot.count()).isEqualTo(expectedCallDurationHistogram.count());
+ assertThat(callDurationSnapshot.total(MILLISECONDS)).isEqualTo(expectedCallDurationHistogram.total());
+ assertThat(callDurationSnapshot.histogramCounts()).contains(new CountAtBucket(4.0E7, 1));
+ }
+}
diff --git a/grpc-server-spring-boot-autoconfigure/src/test/java/net/devh/boot/grpc/server/serverfactory/AbstractGrpcServerFactoryTest.java b/grpc-server-spring-boot-starter/src/test/java/net/devh/boot/grpc/server/serverfactory/AbstractGrpcServerFactoryTest.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/test/java/net/devh/boot/grpc/server/serverfactory/AbstractGrpcServerFactoryTest.java
rename to grpc-server-spring-boot-starter/src/test/java/net/devh/boot/grpc/server/serverfactory/AbstractGrpcServerFactoryTest.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/test/java/net/devh/boot/grpc/server/serverfactory/GrpcServerLifecycleTest.java b/grpc-server-spring-boot-starter/src/test/java/net/devh/boot/grpc/server/serverfactory/GrpcServerLifecycleTest.java
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/test/java/net/devh/boot/grpc/server/serverfactory/GrpcServerLifecycleTest.java
rename to grpc-server-spring-boot-starter/src/test/java/net/devh/boot/grpc/server/serverfactory/GrpcServerLifecycleTest.java
diff --git a/grpc-server-spring-boot-autoconfigure/src/test/resources/logback-test.xml b/grpc-server-spring-boot-starter/src/test/resources/logback-test.xml
similarity index 100%
rename from grpc-server-spring-boot-autoconfigure/src/test/resources/logback-test.xml
rename to grpc-server-spring-boot-starter/src/test/resources/logback-test.xml
diff --git a/settings.gradle b/settings.gradle
index 7897a0ad0..c904b918c 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,9 +1,7 @@
rootProject.name = 'grpc-spring-boot-starter'
include "grpc-common-spring-boot"
-include "grpc-client-spring-boot-autoconfigure"
include "grpc-client-spring-boot-starter"
-include "grpc-server-spring-boot-autoconfigure"
include "grpc-server-spring-boot-starter"
include "tests"
@@ -17,3 +15,6 @@ include "examples:cloud-grpc-client"
include "examples:cloud-grpc-server"
include "examples:security-grpc-client"
include "examples:security-grpc-server"
+include "examples:grpc-observability:backend"
+include "examples:grpc-observability:frontend"
+include "examples:grpc-observability:proto"
diff --git a/tests/src/test/java/net/devh/boot/grpc/test/interceptor/DefaultClientInterceptorTest.java b/tests/src/test/java/net/devh/boot/grpc/test/interceptor/DefaultClientInterceptorTest.java
new file mode 100644
index 000000000..17a51b931
--- /dev/null
+++ b/tests/src/test/java/net/devh/boot/grpc/test/interceptor/DefaultClientInterceptorTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2016-2023 The gRPC-Spring 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 net.devh.boot.grpc.test.interceptor;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.context.ApplicationContext;
+import org.springframework.test.annotation.DirtiesContext;
+import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
+
+import io.grpc.ClientInterceptor;
+import io.micrometer.core.instrument.binder.grpc.MetricCollectingClientInterceptor;
+import io.micrometer.core.instrument.binder.grpc.ObservationGrpcClientInterceptor;
+import net.devh.boot.grpc.client.autoconfigure.GrpcClientAutoConfiguration;
+import net.devh.boot.grpc.client.interceptor.GlobalClientInterceptorRegistry;
+import net.devh.boot.grpc.client.metrics.MetricsClientInterceptor;
+
+@SpringBootTest
+@SpringJUnitConfig(classes = {GrpcClientAutoConfiguration.class})
+@EnableAutoConfiguration
+@DirtiesContext
+public class DefaultClientInterceptorTest {
+
+ @Autowired
+ private ApplicationContext applicationContext;
+
+ @Autowired
+ private GlobalClientInterceptorRegistry registry;
+
+ @Test
+ void testDefaultInterceptors() {
+ final List expected = new ArrayList<>();
+ expected.add(this.applicationContext.getBean(MetricCollectingClientInterceptor.class));
+ expected.add(this.applicationContext.getBean(MetricsClientInterceptor.class));
+ expected.add(this.applicationContext.getBean(ObservationGrpcClientInterceptor.class));
+
+ final List actual = new ArrayList<>(this.registry.getClientInterceptors());
+ assertThat(actual).containsExactlyInAnyOrderElementsOf(expected);
+ }
+}