diff --git a/.github/workflows/build-master.yml b/.github/workflows/build-master.yml index f7407e921..b61356a86 100644 --- a/.github/workflows/build-master.yml +++ b/.github/workflows/build-master.yml @@ -17,10 +17,10 @@ jobs: uses: actions/checkout@v4 - name: Validate Gradle wrapper - uses: gradle/wrapper-validation-action@v1 + uses: gradle/wrapper-validation-action@v3 - name: Set up java ${{ matrix.java }} - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: 'temurin' java-version: ${{ matrix.java }} @@ -30,13 +30,13 @@ jobs: run: openssl aes-256-cbc -K ${{ secrets.ENCRYPTED_KEY }} -iv ${{ secrets.ENCRYPTED_IV }} -in private.key.enc -out ./private.key -d && gpg --batch --import ./private.key || echo - name: Build with Gradle - uses: gradle/gradle-build-action@v2 + uses: gradle/actions/setup-gradle@v3 with: arguments: --scan --stacktrace --warning-mode=all build - name: Deploy with Gradle if: ${{ matrix.java == '17' }} - uses: gradle/gradle-build-action@v2 + uses: gradle/actions/setup-gradle@v3 with: arguments: --scan publish -x check -Psigning.gnupg.executable=gpg -Psigning.gnupg.keyName=${{ secrets.GPG_NAME }} -Psigning.gnupg.passphrase=${{ secrets.GPG_PASSWORD }} env: diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index cb6efeceb..8d457d62f 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -14,22 +14,22 @@ jobs: uses: actions/checkout@v4 - name: Validate Gradle wrapper - uses: gradle/wrapper-validation-action@v1 + uses: gradle/wrapper-validation-action@v3 - name: Set up java ${{ matrix.java }} - uses: actions/setup-java@v3 + uses: actions/setup-java@v4 with: distribution: temurin java-version: ${{ matrix.java }} check-latest: true - name: Build with Gradle - uses: gradle/gradle-build-action@v2 + uses: gradle/actions/setup-gradle@v3 with: arguments: --scan --stacktrace --warning-mode=all build # Avoid publish errors when upgrading gradle version and dependencyManager plugin - name: Try publishToMavenLocal - uses: gradle/gradle-build-action@v2 + uses: gradle/actions/setup-gradle@v3 with: arguments: publishToMavenLocal diff --git a/README-zh-CN.md b/README-zh-CN.md index 70e9e0af8..83bf1a31f 100644 --- a/README-zh-CN.md +++ b/README-zh-CN.md @@ -2,7 +2,7 @@ [![Build master branch](https://github.com/grpc-ecosystem/grpc-spring/workflows/Build%20master%20branch/badge.svg)](https://github.com/grpc-ecosystem/grpc-spring/actions) [![Maven Central with version prefix filter](https://img.shields.io/maven-central/v/net.devh/grpc-spring-boot-starter.svg)](http://search.maven.org/#search%7Cga%7C1%7Cg%3A%22net.devh%22%20grpc) [![License](https://img.shields.io/github/license/mashape/apistatus.svg)](LICENSE) [![Crowdin](https://badges.crowdin.net/grpc-spring-boot-starter/localized.svg)](https://crowdin.com/project/grpc-spring-boot-starter) -[![Client-Javadoc](https://www.javadoc.io/badge/net.devh/grpc-client-spring-boot-autoconfigure.svg?label=Client-Javadoc)](https://www.javadoc.io/doc/net.devh/grpc-client-spring-boot-autoconfigure) [![Server-Javadoc](https://www.javadoc.io/badge/net.devh/grpc-server-spring-boot-autoconfigure.svg?label=Server-Javadoc)](https://www.javadoc.io/doc/net.devh/grpc-server-spring-boot-autoconfigure) [![Common-Javadoc](https://www.javadoc.io/badge/net.devh/grpc-common-spring-boot.svg?label=Common-Javadoc)](https://www.javadoc.io/doc/net.devh/grpc-common-spring-boot) +[![Client-Javadoc](https://www.javadoc.io/badge/net.devh/grpc-client-spring-boot-starter.svg?label=Client-Javadoc)](https://www.javadoc.io/doc/net.devh/grpc-client-spring-boot-starter) [![Server-Javadoc](https://www.javadoc.io/badge/net.devh/grpc-server-spring-boot-starter.svg?label=Server-Javadoc)](https://www.javadoc.io/doc/net.devh/grpc-server-spring-boot-starter) [![Common-Javadoc](https://www.javadoc.io/badge/net.devh/grpc-common-spring-boot.svg?label=Common-Javadoc)](https://www.javadoc.io/doc/net.devh/grpc-common-spring-boot) README: [English](README.md) | [中文](README-zh-CN.md) @@ -40,7 +40,7 @@ README: [English](README.md) | [中文](README-zh-CN.md) ## 版本 -最新版本是 `2.15.0.RELEASE` 它能跟 Spring-Boot `2.7.16` 和 Spring-Cloud `2021.0.8` 搭配使用。 但它也与各种其他版本兼容。 我们的 [文档](https://yidongnan.github.io/grpc-spring-boot-starter/en/versions.html) 中可以找到所有版本及其相应的库版本的概览。 +最新版本是 `3.1.0.RELEASE` 它能跟 Spring-Boot `3.2.4` 和 Spring-Cloud `2023.0.0` 搭配使用。 但它也与各种其他版本兼容。 我们的 [文档](https://yidongnan.github.io/grpc-spring-boot-starter/en/versions.html) 中可以找到所有版本及其相应的库版本的概览。 **注意:** 该项目也可以在没有 Spring-Boot 的情况下使用,但是您需要手动配置一些 bean。 @@ -54,7 +54,7 @@ README: [English](README.md) | [中文](README-zh-CN.md) net.devh grpc-spring-boot-starter - 2.15.0.RELEASE + 3.1.0.RELEASE ```` @@ -62,7 +62,7 @@ README: [English](README.md) | [中文](README-zh-CN.md) ````gradle dependencies { - implementation 'net.devh:grpc-spring-boot-starter:2.15.0.RELEASE' + implementation 'net.devh:grpc-spring-boot-starter:3.1.0.RELEASE' } ```` @@ -74,7 +74,7 @@ dependencies { net.devh grpc-server-spring-boot-starter - 2.15.0.RELEASE + 3.1.0.RELEASE ```` @@ -82,7 +82,7 @@ dependencies { ````gradle dependencies { - implementation 'net.devh:grpc-server-spring-boot-starter:2.15.0.RELEASE' + implementation 'net.devh:grpc-server-spring-boot-starter:3.1.0.RELEASE' } ```` @@ -102,7 +102,7 @@ public class GrpcServerService extends GreeterGrpc.GreeterImplBase { } ```` -默认情况下,gRPC 服务器将监听端口 `9090`。 端口的配置和其他的 [设置](grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/config/GrpcServerProperties.java) 可以通过 Spring 的属性机制进行更改。 服务端的配置使用 `grpc.server.` 前缀。 +默认情况下,gRPC 服务器将监听端口 `9090`。 端口的配置和其他的 [设置](grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/config/GrpcServerProperties.java) 可以通过 Spring 的属性机制进行更改。 服务端的配置使用 `grpc.server.` 前缀。 详情请参阅我们的[文档](https://yidongnan.github.io/grpc-spring-boot-starter/)。 @@ -114,7 +114,7 @@ public class GrpcServerService extends GreeterGrpc.GreeterImplBase { net.devh grpc-client-spring-boot-starter - 2.15.0.RELEASE + 3.1.0.RELEASE ```` @@ -122,7 +122,7 @@ public class GrpcServerService extends GreeterGrpc.GreeterImplBase { ````gradle dependencies { - compile 'net.devh:grpc-client-spring-boot-starter:2.15.0.RELEASE' + compile 'net.devh:grpc-client-spring-boot-starter:3.1.0.RELEASE' } ```` @@ -145,7 +145,7 @@ HelloReply response = stub.sayHello(HelloRequest.newBuilder().setName(name).buil 可以单独配置每个客户端的目标地址。 但在某些情况下,您可以仅依靠默认配置。 您可以通过 `NameResolver.Factory` Bean 类自定义默认的 url 映射。 如果您没有配置那个Bean,那么默认的 uri 将使用默认方案和名称(如:`dns:`): -这些配置和其他的 [设置](grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/config/GrpcChannelProperties.java) 可以通过 Spring 的属性机制进行更改。 客户端使用`grpc.client.(serverName)。` 前缀。 +这些配置和其他的 [设置](grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/config/GrpcChannelProperties.java) 可以通过 Spring 的属性机制进行更改。 客户端使用`grpc.client.(serverName)。` 前缀。 详情请参阅我们的[文档](https://yidongnan.github.io/grpc-spring-boot-starter/)。 diff --git a/README.md b/README.md index 3581a02a5..a64049043 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,9 @@ +# Survey - gRPC/Spring + +Dear gRPC/Spring users, in order to enhance the user experience of +grpc-ecosystem/grpc-spring, we have developed [this survey](https://docs.google.com/forms/d/e/1FAIpQLSfHgvh_Z0_wwX7JQLERanJ-AAXjiKh23_kSI3Rl5mnKVQ8Bpw/viewform?resourcekey=0-mEilI6lFvIfVXiUniEyCog) as a means of +establishing a direct line of communication. Your feedback is highly appreciated. + # gRPC Spring Boot Starter [![Build master branch](https://github.com/grpc-ecosystem/grpc-spring/workflows/Build%20master%20branch/badge.svg)](https://github.com/grpc-ecosystem/grpc-spring/actions) @@ -5,13 +11,13 @@ [![License](https://img.shields.io/github/license/mashape/apistatus.svg)](LICENSE) [![Crowdin](https://badges.crowdin.net/grpc-spring-boot-starter/localized.svg)](https://crowdin.com/project/grpc-spring-boot-starter) -[![Client-Javadoc](https://www.javadoc.io/badge/net.devh/grpc-client-spring-boot-autoconfigure.svg?label=Client-Javadoc)](https://www.javadoc.io/doc/net.devh/grpc-client-spring-boot-autoconfigure) -[![Server-Javadoc](https://www.javadoc.io/badge/net.devh/grpc-server-spring-boot-autoconfigure.svg?label=Server-Javadoc)](https://www.javadoc.io/doc/net.devh/grpc-server-spring-boot-autoconfigure) +[![Client-Javadoc](https://www.javadoc.io/badge/net.devh/grpc-client-spring-boot-starter.svg?label=Client-Javadoc)](https://www.javadoc.io/doc/net.devh/grpc-client-spring-boot-starter) +[![Server-Javadoc](https://www.javadoc.io/badge/net.devh/grpc-server-spring-boot-starter.svg?label=Server-Javadoc)](https://www.javadoc.io/doc/net.devh/grpc-server-spring-boot-starter) [![Common-Javadoc](https://www.javadoc.io/badge/net.devh/grpc-common-spring-boot.svg?label=Common-Javadoc)](https://www.javadoc.io/doc/net.devh/grpc-common-spring-boot) README: [English](README.md) | [中文](README-zh-CN.md) -**Documentation:** [English](https://yidongnan.github.io/grpc-spring-boot-starter/en/) | [中文](https://yidongnan.github.io/grpc-spring-boot-starter/zh-CN/) +**Documentation:** [English](https://grpc-ecosystem.github.io/grpc-spring/en/) | [中文](https://grpc-ecosystem.github.io/grpc-spring/zh-CN/) ## Features @@ -50,7 +56,7 @@ README: [English](README.md) | [中文](README-zh-CN.md) ## Versions -The latest version is `2.15.0.RELEASE` it was compiled with spring-boot `2.7.16` and spring-cloud `2021.0.8` +The latest version is `3.1.0.RELEASE` it was compiled with spring-boot `3.2.4` and spring-cloud `2023.0.0` but it is also compatible with a large variety of other versions. An overview of all versions and their respective library versions can be found in our [documentation](https://yidongnan.github.io/grpc-spring-boot-starter/en/versions.html). @@ -66,7 +72,7 @@ To add a dependency using Maven, use the following: net.devh grpc-spring-boot-starter - 2.15.0.RELEASE + 3.1.0.RELEASE ```` @@ -74,7 +80,7 @@ To add a dependency using Gradle: ````gradle dependencies { - implementation 'net.devh:grpc-spring-boot-starter:2.15.0.RELEASE' + implementation 'net.devh:grpc-spring-boot-starter:3.1.0.RELEASE' } ```` @@ -86,7 +92,7 @@ To add a dependency using Maven, use the following: net.devh grpc-server-spring-boot-starter - 2.15.0.RELEASE + 3.1.0.RELEASE ```` @@ -94,7 +100,7 @@ To add a dependency using Gradle: ````gradle dependencies { - implementation 'net.devh:grpc-server-spring-boot-starter:2.15.0.RELEASE' + implementation 'net.devh:grpc-server-spring-boot-starter:3.1.0.RELEASE' } ```` @@ -115,7 +121,7 @@ public class GrpcServerService extends GreeterGrpc.GreeterImplBase { ```` By default, the grpc server will listen to port `9090`. These and other -[settings](grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/config/GrpcServerProperties.java) +[settings](grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/config/GrpcServerProperties.java) can be changed via Spring's property mechanism. The server uses the `grpc.server.` prefix. Refer to our [documentation](https://yidongnan.github.io/grpc-spring-boot-starter/) for more details. @@ -128,7 +134,7 @@ To add a dependency using Maven, use the following: net.devh grpc-client-spring-boot-starter - 2.15.0.RELEASE + 3.1.0.RELEASE ```` @@ -136,7 +142,7 @@ To add a dependency using Gradle: ````gradle dependencies { - compile 'net.devh:grpc-client-spring-boot-starter:2.15.0.RELEASE' + compile 'net.devh:grpc-client-spring-boot-starter:3.1.0.RELEASE' } ```` @@ -164,7 +170,7 @@ You can customize the default url mapping via `NameResolver.Factory` beans. If y then the default uri will be guessed using the default scheme and the name (e.g.: `dns:/`): These and other -[settings](grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/config/GrpcChannelProperties.java) +[settings](grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/config/GrpcChannelProperties.java) can be changed via Spring's property mechanism. The clients use the `grpc.client.(serverName).` prefix. Refer to our [documentation](https://yidongnan.github.io/grpc-spring-boot-starter/) for more details. diff --git a/build.gradle b/build.gradle index 95007683a..0944fb4fb 100644 --- a/build.gradle +++ b/build.gradle @@ -8,30 +8,30 @@ buildscript { } } ext { - projectVersion = '3.0.0-SNAPSHOT' + projectVersion = '3.1.0.RELEASE' // https://github.com/grpc/grpc-java/releases - grpcVersion = '1.58.0' + grpcVersion = '1.63.0' // https://github.com/google/guava/releases - guavaVersion = '32.1.2-jre' + guavaVersion = '33.1.0-jre' // https://github.com/protocolbuffers/protobuf/releases - protobufVersion = '3.24.3' + protobufVersion = '3.25.3' protobufGradlePluginVersion = '0.9.4' // https://github.com/spring-projects/spring-boot/releases - springBootVersion = '3.1.4' + springBootVersion = '3.2.5' // https://github.com/spring-cloud/spring-cloud-release/releases - springCloudVersion = '2022.0.4' + springCloudVersion = '2023.0.0' // https://github.com/alibaba/spring-cloud-alibaba/releases - springCloudAlibabaNacosVersion = '2021.1' + springCloudAlibabaNacosVersion = '2022.0.0.0' - lombokPluginVersion = '8.4' - versioningPluginVersion = '3.0.0' - versionsPluginVersion = '0.49.0' + lombokPluginVersion = '8.6' + versioningPluginVersion = '3.1.0' + versionsPluginVersion = '0.51.0' // https://github.com/JetBrains/kotlin/releases - kotlinVersion = "1.8.22" + kotlinVersion = "1.9.23" } } @@ -39,12 +39,12 @@ plugins { id 'java' id 'java-library' id 'org.springframework.boot' version "${springBootVersion}" apply false - id 'io.spring.dependency-management' version '1.1.3' + id 'io.spring.dependency-management' version '1.1.4' id 'net.nemerosa.versioning' version "${versioningPluginVersion}" id 'com.google.protobuf' version "${protobufGradlePluginVersion}" id 'io.freefair.lombok' version "${lombokPluginVersion}" apply false id 'com.github.ben-manes.versions' version "${versionsPluginVersion}" // gradle dependencyUpdates - id 'com.diffplug.spotless' version '6.22.0' + id 'com.diffplug.spotless' version '6.25.0' id 'org.jetbrains.kotlin.jvm' version "${kotlinVersion}" apply false } @@ -61,7 +61,7 @@ if (hasProperty('buildScan')) { wrapper { // Update using: // ./gradlew wrapper --gradle-version=8.4 --distribution-type=bin - gradleVersion = '8.4' + gradleVersion = '8.7' } def buildTimeAndDate = OffsetDateTime.now() @@ -181,7 +181,7 @@ allprojects { project -> mavenBom "com.google.protobuf:protobuf-bom:${protobufVersion}" mavenBom "com.google.guava:guava-bom:${guavaVersion}" mavenBom "io.grpc:grpc-bom:${grpcVersion}" - mavenBom "org.junit:junit-bom:5.10.0" + mavenBom "org.junit:junit-bom:5.10.2" mavenBom "org.jetbrains.kotlin:kotlin-bom:${kotlinVersion}" } } @@ -217,7 +217,7 @@ allprojects { project -> } } - if (project.name == 'grpc-common-spring-boot' || project.name == 'grpc-client-spring-boot-autoconfigure' || project.name == 'grpc-server-spring-boot-autoconfigure') { + if (project.name == 'grpc-common-spring-boot' || project.name == 'grpc-client-spring-boot-starter' || project.name == 'grpc-server-spring-boot-starter') { java { registerFeature('optionalSupport') { usingSourceSet(sourceSets.main) diff --git a/docs/assets/images/client-project-setup.dot b/docs/assets/images/client-project-setup.dot index b0ae8e1ce..3dad16f90 100644 --- a/docs/assets/images/client-project-setup.dot +++ b/docs/assets/images/client-project-setup.dot @@ -39,7 +39,7 @@ digraph serversetup { fillColor="white"; fontcolor="gray50"; - serviceimpl [label="Service implementations", color="gray50", fontcolor="gray50",fillcolor="white",width="3", URL="https://github.com/grpc-ecosystem/grpc-spring/blob/master/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/service/GrpcService.java#L49", target="_blank"]; + serviceimpl [label="Service implementations", color="gray50", fontcolor="gray50",fillcolor="white",width="3", URL="https://github.com/grpc-ecosystem/grpc-spring/blob/master/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/service/GrpcService.java#L49", target="_blank"]; servicemodel -> serviceimpl [style=dashed, color="gray50", dir=back]; @@ -49,7 +49,7 @@ digraph serversetup { label="Client-Projects"; - clientfield [label="Client/Stub usage", width="3", URL="https://github.com/grpc-ecosystem/grpc-spring/blob/master/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/inject/GrpcClient.java#L69", target="_blank"]; + clientfield [label="Client/Stub usage", width="3", URL="https://github.com/grpc-ecosystem/grpc-spring/blob/master/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/inject/GrpcClient.java#L69", target="_blank"]; servicemodel:se -> clientfield[style=dashed, dir=back]; servicemodel:se -> clientfield[style=dashed, dir=back, weight=0]; diff --git a/docs/assets/images/client-project-setup.svg b/docs/assets/images/client-project-setup.svg index b30fe187f..a4fa219df 100644 --- a/docs/assets/images/client-project-setup.svg +++ b/docs/assets/images/client-project-setup.svg @@ -101,7 +101,7 @@ serviceimpl - + Service implementations @@ -114,7 +114,7 @@ clientfield - + Client/Stub usage diff --git a/docs/assets/images/server-project-setup.dot b/docs/assets/images/server-project-setup.dot index b2b6f8e32..182ab903c 100644 --- a/docs/assets/images/server-project-setup.dot +++ b/docs/assets/images/server-project-setup.dot @@ -36,7 +36,7 @@ digraph serversetup { label="Server-Project" - serviceimpl [label="Service implementations", width="3", URL="https://github.com/grpc-ecosystem/grpc-spring/blob/master/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/service/GrpcService.java#L49", target="_blank"]; + serviceimpl [label="Service implementations", width="3", URL="https://github.com/grpc-ecosystem/grpc-spring/blob/master/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/service/GrpcService.java#L49", target="_blank"]; servicemodel -> serviceimpl [style=dashed, dir=back]; @@ -49,7 +49,7 @@ digraph serversetup { fillColor="white"; fontcolor="gray50"; - clientfield [label="Client/Stub usage", width="3", color="gray50", fontcolor="gray50",fillcolor="white", URL="https://github.com/grpc-ecosystem/grpc-spring/blob/master/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/inject/GrpcClient.java#L69", target="_blank"]; + clientfield [label="Client/Stub usage", width="3", color="gray50", fontcolor="gray50",fillcolor="white", URL="https://github.com/grpc-ecosystem/grpc-spring/blob/master/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/inject/GrpcClient.java#L69", target="_blank"]; servicemodel:se -> clientfield[style=dashed, dir=back, color="gray50"]; servicemodel:se -> clientfield[style=dashed, dir=back, color="gray50", weight=0]; diff --git a/docs/assets/images/server-project-setup.svg b/docs/assets/images/server-project-setup.svg index 71935033c..86ae30ccc 100644 --- a/docs/assets/images/server-project-setup.svg +++ b/docs/assets/images/server-project-setup.svg @@ -101,7 +101,7 @@ serviceimpl - + Service implementations @@ -114,7 +114,7 @@ clientfield - + Client/Stub usage diff --git a/docs/en/actuator.md b/docs/en/actuator.md index fa45bfdb7..58c2593f8 100644 --- a/docs/en/actuator.md +++ b/docs/en/actuator.md @@ -73,6 +73,50 @@ Once the dependencies are added grpc-spring-boot-starter will automatically conf - `methodType`: The type of the requested grpc method. - `statusCode`: Response `Status.Code` +## gRPC A66 Metrics + +In addition to above listed metrics, once the dependencies are added grpc-spring-boot-starter will automatically configure to gather [gRPC A66](https://github.com/grpc/proposal/blob/master/A66-otel-stats.md) metrics. + +### Client Metrics + +### Counter + +- `grpc.client.attempt.started`: The total number of RPC attempts started, including those that have not completed. + - Tags: `grpc.method` + +### Distribution + +- `grpc.client.attempt.sent_total_compressed_message_size`: Total bytes (compressed but not encrypted) sent across all request messages (metadata excluded) per RPC attempt. + - Tags: `grpc.method`, `grpc.status` +- `grpc.client.attempt.rcvd_total_compressed_message_size`: Total bytes (compressed but not encrypted) received across all response messages (metadata excluded) per RPC attempt. + - Tags: `grpc.method`, `grpc.status` + +### Timer + +- `grpc.client.attempt.duration`: The total time taken to complete an RPC attempt including the time it takes to pick a sub channel. + - Tags: `grpc.method`, `grpc.status` +- `grpc.client.call.duration`: The total time taken by gRPC library to complete an RPC from the application’s perspective. + - Tags: `grpc.method`, `grpc.status` + +### Server Metrics + +### Counter + +- `grpc.server.call.started`: The total number of RPCs started, including those that have not completed. + - Tags: `grpc.method` + +### Distribution + +- `grpc.server.call.sent_total_compressed_message_size`: Total bytes (compressed but not encrypted) sent across all response messages (metadata excluded) per RPC. + - Tags: `grpc.method`, `grpc.status` +- `grpc.server.call.rcvd_total_compressed_message_size`: Total bytes (compressed but not encrypted) received across all request messages (metadata excluded) per RPC. + - Tags: `grpc.method`, `grpc.status` + +### Timer + +- `grpc.server.call.duration`: The total time an RPC takes from the server transport’s perspective. + - Tags: `grpc.method`, `grpc.status` + ### Viewing the metrics You can view the grpc metrics along with your other metrics at `/actuator/metrics` (requires a web-server) or via JMX. @@ -150,6 +194,8 @@ net.devh.boot.grpc.client.autoconfigure.GrpcClientMetricAutoConfiguration,\ net.devh.boot.grpc.server.autoconfigure.GrpcServerMetricAutoConfiguration ```` +You can opt out from collecting gRPC A66 metrics using `grpc.metricsA66Enabled=false`. + ---------- [<- Back to Index](index.md) diff --git a/docs/en/brave.md b/docs/en/brave.md index d58080051..efda8bb8c 100644 --- a/docs/en/brave.md +++ b/docs/en/brave.md @@ -73,7 +73,7 @@ Spring-Cloud-Sleuth provides a few classes such as [`SpringAwareManagedChannelBuilder`](https://javadoc.io/page/org.springframework.cloud/spring-cloud-sleuth-core/latest/org/springframework/cloud/sleuth/instrument/grpc/SpringAwareManagedChannelBuilder.html), those classes solely exists for compatibility reasons with a different library. Do not use them with this library. grpc-spring-boot-starter provides the same/extended functionality out of the box with the -[`GrpcChannelFactory`](https://javadoc.io/page/net.devh/grpc-client-spring-boot-autoconfigure/latest/net/devh/boot/grpc/client/channelfactory/GrpcChannelFactory.html) +[`GrpcChannelFactory`](https://javadoc.io/page/net.devh/grpc-client-spring-boot-starter/latest/net/devh/boot/grpc/client/channelfactory/GrpcChannelFactory.html) and related classes. See also [sleuth's javadoc note](https://github.com/spring-cloud/spring-cloud-sleuth/blob/59216c32f7848ec337fb68d1dbec8e87eeb6bf59/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/grpc/SpringAwareManagedChannelBuilder.java#L31-L34). diff --git a/docs/en/client/configuration.md b/docs/en/client/configuration.md index e7a8499e4..00ad2babd 100644 --- a/docs/en/client/configuration.md +++ b/docs/en/client/configuration.md @@ -31,12 +31,12 @@ spring's `@ConfigurationProperties` mechanism. You can find all build-in configuration properties here: -- [`GrpcChannelsProperties`](https://javadoc.io/page/net.devh/grpc-client-spring-boot-autoconfigure/latest/net/devh/boot/grpc/client/config/GrpcChannelsProperties.html) -- [`GrpcChannelProperties`](https://javadoc.io/page/net.devh/grpc-client-spring-boot-autoconfigure/latest/net/devh/boot/grpc/client/config/GrpcChannelProperties.html) -- [`GrpcServerProperties.Security`](https://static.javadoc.io/net.devh/grpc-client-spring-boot-autoconfigure/latest/net/devh/boot/grpc/client/config/GrpcChannelProperties.Security.html) +- [`GrpcChannelsProperties`](https://javadoc.io/page/net.devh/grpc-client-spring-boot-starter/latest/net/devh/boot/grpc/client/config/GrpcChannelsProperties.html) +- [`GrpcChannelProperties`](https://javadoc.io/page/net.devh/grpc-client-spring-boot-starter/latest/net/devh/boot/grpc/client/config/GrpcChannelProperties.html) +- [`GrpcServerProperties.Security`](https://static.javadoc.io/net.devh/grpc-client-spring-boot-starter/latest/net/devh/boot/grpc/client/config/GrpcChannelProperties.Security.html) If you prefer to read the sources instead, you can do so -[here](https://github.com/grpc-ecosystem/grpc-spring/blob/master/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/config/GrpcChannelProperties.java#L58). +[here](https://github.com/grpc-ecosystem/grpc-spring/blob/master/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/config/GrpcChannelProperties.java#L58). The properties for the channels are all prefixed with `grpc.client.__name__.` and `grpc.client.__name__.security.` respectively. The channel name is taken from the `@GrpcClient("__name__")` annotation. @@ -69,7 +69,7 @@ There are a number of supported schemes, that you can use to determine the targe - `discovery` (Prio 6): \ (Optional) Uses spring-cloud's `DiscoveryClient` to lookup appropriate targets. The connections will be refreshed automatically during `HeartbeatEvent`s. Uses the `gRPC_port` metadata to determine the port, otherwise uses the - service port. \ + service port. Uses the `gRPC_service_config` metadata to determine [service config](https://grpc.github.io/grpc/core/md_doc_service_config.html). \ Example: `discovery:///service-name` - `self` (Prio 0): \ The self address or scheme is a keyword that is available, if you also use `grpc-server-spring-boot-starter` and @@ -322,7 +322,7 @@ using a custom `NameResolverProvider`. > **Note:** This can only be used to decide this on an application level and not on a per request level. This library provides some `NameResolverProvider`s itself so you can use them as a -[reference](https://github.com/grpc-ecosystem/grpc-spring/tree/master/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/nameresolver). +[reference](https://github.com/grpc-ecosystem/grpc-spring/tree/master/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/nameresolver). You can register your `NameResolverProvider` by adding it to `META-INF/services/io.grpc.NameResolverProvider` for Java's `ServiceLoader` or adding it your spring context. If you wish to use some spring beans inside your `NameResolver`, then diff --git a/docs/en/client/getting-started.md b/docs/en/client/getting-started.md index 9fb2a49dc..7671c4845 100644 --- a/docs/en/client/getting-started.md +++ b/docs/en/client/getting-started.md @@ -26,7 +26,7 @@ This section deals with how to get Spring to connect to a grpc server and manage Before we start adding the dependencies lets start with some of our recommendation for your project setup. -![project setup](/grpc-spring-boot-starter/assets/images/client-project-setup.svg) +![project setup](/grpc-spring/assets/images/client-project-setup.svg) We recommend splitting your project into 2-3 separate modules. @@ -102,7 +102,7 @@ This section assumes that you have already defined and generated your [Protobuf The following list contains all features that you might encounter on the client side. If you don't wish to use any advanced features, then the first two elements are probably all you need to use. -- [`@GrpcClient`](https://javadoc.io/page/net.devh/grpc-client-spring-boot-autoconfigure/latest/net/devh/boot/grpc/client/inject/GrpcClient.html): +- [`@GrpcClient`](https://javadoc.io/page/net.devh/grpc-client-spring-boot-starter/latest/net/devh/boot/grpc/client/inject/GrpcClient.html): The annotation that marks fields and setters for auto injection of clients. Support for constructor and `@Bean` factory method parameters is experimental. Supports `Channel`s, and all kinds of `Stub`s. @@ -110,7 +110,7 @@ If you don't wish to use any advanced features, then the first two elements are **Note:** Services provided by the same application can only be accessed/called in/after the `ApplicationStartedEvent`. Stubs connecting to services outside of the application can be used earlier; starting with `@PostConstruct` / `InitializingBean#afterPropertiesSet()`. -- [`@GrpcClientBean`](https://javadoc.io/page/net.devh/grpc-client-spring-boot-autoconfigure/latest/net/devh/boot/grpc/client/inject/GrpcClientBean.html): +- [`@GrpcClientBean`](https://javadoc.io/page/net.devh/grpc-client-spring-boot-starter/latest/net/devh/boot/grpc/client/inject/GrpcClientBean.html): The annotation helps to register `@GrpcClient` beans in the Spring context to be used with `@Autowired` and `@Qualifier`. The annotation can be repeatedly added to any of your `@Configuration` classes. - [`Channel`](https://javadoc.io/page/io.grpc/grpc-all/latest/io/grpc/Channel.html): @@ -128,9 +128,9 @@ If you don't wish to use any advanced features, then the first two elements are Intercepts every call before they are handed to the `Channel`. Can be used for logging, monitoring, metadata handling, and request/response rewriting. grpc-spring-boot-starter will automatically pick up all client interceptors that are annotated with - [`@GrpcGlobalClientInterceptor`](https://javadoc.io/page/net.devh/grpc-client-spring-boot-autoconfigure/latest/net/devh/boot/grpc/client/interceptor/GrpcGlobalClientInterceptor.html) + [`@GrpcGlobalClientInterceptor`](https://javadoc.io/page/net.devh/grpc-client-spring-boot-starter/latest/net/devh/boot/grpc/client/interceptor/GrpcGlobalClientInterceptor.html) or are manually registered to the - [`GlobalClientInterceptorRegistry`](https://javadoc.io/page/net.devh/grpc-client-spring-boot-autoconfigure/latest/net/devh/boot/grpc/client/interceptor/GlobalClientInterceptorRegistry.html). + [`GlobalClientInterceptorRegistry`](https://javadoc.io/page/net.devh/grpc-client-spring-boot-starter/latest/net/devh/boot/grpc/client/interceptor/GlobalClientInterceptorRegistry.html). See also [Configuration -> ClientInterceptor](configuration.md#clientinterceptor). - [`CallCredentials`](https://javadoc.io/page/io.grpc/grpc-all/latest/io/grpc/CallCredentials.html): A potentially active component that manages the authentication for the calls. It can be used to store credentials or @@ -138,12 +138,12 @@ If you don't wish to use any advanced features, then the first two elements are as OAuth) to authorize the actual request. In addition to that, it is able to renew the token, if it expired and re-sent the request. If exactly one `CallCredentials` bean is present on your application context then spring will automatically attach it to all `Stub`s (**NOT** `Channel`s). The - [`CallCredentialsHelper`](https://javadoc.io/page/net.devh/grpc-client-spring-boot-autoconfigure/latest/net/devh/boot/grpc/client/security/CallCredentialsHelper.html) + [`CallCredentialsHelper`](https://javadoc.io/page/net.devh/grpc-client-spring-boot-starter/latest/net/devh/boot/grpc/client/security/CallCredentialsHelper.html) utility class helps you to create commonly used `CallCredentials` types and related `StubTransformer`. -- [`StubFactory`](https://javadoc.io/page/net.devh/grpc-client-spring-boot-autoconfigure/latest/net/devh/boot/grpc/client/stubfactory/StubFactory.html): +- [`StubFactory`](https://javadoc.io/page/net.devh/grpc-client-spring-boot-starter/latest/net/devh/boot/grpc/client/stubfactory/StubFactory.html): A factory that can be used to create a specfic `Stub` type from a `Channel`. Multiple `StubFactory`s can be registered to support different stub types. See also [Configuration -> StubFactory](configuration.md#stubfactory). -- [`StubTransformer`](https://javadoc.io/page/net.devh/grpc-client-spring-boot-autoconfigure/latest/net/devh/boot/grpc/client/inject/StubTransformer.html): +- [`StubTransformer`](https://javadoc.io/page/net.devh/grpc-client-spring-boot-starter/latest/net/devh/boot/grpc/client/inject/StubTransformer.html): A transformer that will be applied to all client `Stub`s before they are injected. See also [Configuration -> StubTransformer](configuration.md#stubtransformer). diff --git a/docs/en/client/security.md b/docs/en/client/security.md index cc4d3ba38..7cde67029 100644 --- a/docs/en/client/security.md +++ b/docs/en/client/security.md @@ -128,7 +128,7 @@ CallCredentials bearerAuthForwardingCredentials() { If you have exactly one `CallCredentials` in your application context, we'll automatically create a `StubTransformer` for you and configure all `Stub`s to use it. If you wish to configure different credentials per stub, then you use our helper methods in the -[`CallCredentialsHelper`](https://javadoc.io/page/net.devh/grpc-client-spring-boot-autoconfigure/latest/net/devh/boot/grpc/client/security/CallCredentialsHelper.html) +[`CallCredentialsHelper`](https://javadoc.io/page/net.devh/grpc-client-spring-boot-starter/latest/net/devh/boot/grpc/client/security/CallCredentialsHelper.html) utility. > **Note:** `StubTransformer`s can only automatically configure injected `Stub`s. They are unable to modify raw diff --git a/docs/en/server/configuration.md b/docs/en/server/configuration.md index 98ef2dc67..ed9753b20 100644 --- a/docs/en/server/configuration.md +++ b/docs/en/server/configuration.md @@ -31,11 +31,11 @@ spring's `@ConfigurationProperties` mechanism. You can find all build-in configuration properties here: -- [`GrpcServerProperties`](https://javadoc.io/page/net.devh/grpc-server-spring-boot-autoconfigure/latest/net/devh/boot/grpc/server/config/GrpcServerProperties.html) -- [`GrpcServerProperties.Security`](https://javadoc.io/page/net.devh/grpc-server-spring-boot-autoconfigure/latest/net/devh/boot/grpc/server/config/GrpcServerProperties.Security.html) +- [`GrpcServerProperties`](https://javadoc.io/page/net.devh/grpc-server-spring-boot-starter/latest/net/devh/boot/grpc/server/config/GrpcServerProperties.html) +- [`GrpcServerProperties.Security`](https://javadoc.io/page/net.devh/grpc-server-spring-boot-starter/latest/net/devh/boot/grpc/server/config/GrpcServerProperties.Security.html) If you prefer to read the sources instead, you can do so -[here](https://github.com/grpc-ecosystem/grpc-spring/blob/master/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/config/GrpcServerProperties.java#L50). +[here](https://github.com/grpc-ecosystem/grpc-spring/blob/master/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/config/GrpcServerProperties.java#L50). The properties for the server are all prefixed with `grpc.server.` and `grpc.server.security.` respectively. diff --git a/docs/en/server/getting-started.md b/docs/en/server/getting-started.md index 7632a4408..a16644b03 100644 --- a/docs/en/server/getting-started.md +++ b/docs/en/server/getting-started.md @@ -28,7 +28,7 @@ This section describes the steps necessary to convert your application into a gr Before we start adding the dependencies lets start with some of our recommendation for your project setup. -![project setup](/grpc-spring-boot-starter/assets/images/server-project-setup.svg) +![project setup](/grpc-spring/assets/images/server-project-setup.svg) We recommend splitting your project into 2-3 separate modules. diff --git a/docs/en/server/security.md b/docs/en/server/security.md index 92c1698cb..5ec85bbe3 100644 --- a/docs/en/server/security.md +++ b/docs/en/server/security.md @@ -111,19 +111,19 @@ your application. In order to support authentication from grpc-clients, you have to define how the clients are allowed to authenticate. You can do so by defining a -[`GrpcAuthenticationReader`](https://javadoc.io/page/net.devh/grpc-server-spring-boot-autoconfigure/latest/net/devh/boot/grpc/server/security/authentication/GrpcAuthenticationReader.html). +[`GrpcAuthenticationReader`](https://javadoc.io/page/net.devh/grpc-server-spring-boot-starter/latest/net/devh/boot/grpc/server/security/authentication/GrpcAuthenticationReader.html). grpc-spring-boot-starter comes with a number of build-in implementations: -- [`AnonymousAuthenticationReader`](https://javadoc.io/page/net.devh/grpc-server-spring-boot-autoconfigure/latest/net/devh/boot/grpc/server/security/authentication/AnonymousAuthenticationReader.html) +- [`AnonymousAuthenticationReader`](https://javadoc.io/page/net.devh/grpc-server-spring-boot-starter/latest/net/devh/boot/grpc/server/security/authentication/AnonymousAuthenticationReader.html) for spring's anonymous auth. -- [`BasicGrpcAuthenticationReader`](https://javadoc.io/page/net.devh/grpc-server-spring-boot-autoconfigure/latest/net/devh/boot/grpc/server/security/authentication/BasicGrpcAuthenticationReader.html) +- [`BasicGrpcAuthenticationReader`](https://javadoc.io/page/net.devh/grpc-server-spring-boot-starter/latest/net/devh/boot/grpc/server/security/authentication/BasicGrpcAuthenticationReader.html) for basic auth. -- [`BearerAuthenticationReader`](https://javadoc.io/page/net.devh/grpc-server-spring-boot-autoconfigure/latest/net/devh/boot/grpc/server/security/authentication/BearerAuthenticationReader.html) +- [`BearerAuthenticationReader`](https://javadoc.io/page/net.devh/grpc-server-spring-boot-starter/latest/net/devh/boot/grpc/server/security/authentication/BearerAuthenticationReader.html) for OAuth and similar protocols. -- [`SSLContextGrpcAuthenticationReader`](https://javadoc.io/page/net.devh/grpc-server-spring-boot-autoconfigure/latest/net/devh/boot/grpc/server/security/authentication/SSLContextGrpcAuthenticationReader.html) +- [`SSLContextGrpcAuthenticationReader`](https://javadoc.io/page/net.devh/grpc-server-spring-boot-starter/latest/net/devh/boot/grpc/server/security/authentication/SSLContextGrpcAuthenticationReader.html) for certificate based authentication. -- [`CompositeGrpcAuthenticationReader`](https://javadoc.io/page/net.devh/grpc-server-spring-boot-autoconfigure/latest/net/devh/boot/grpc/server/security/authentication/CompositeGrpcAuthenticationReader.html) +- [`CompositeGrpcAuthenticationReader`](https://javadoc.io/page/net.devh/grpc-server-spring-boot-starter/latest/net/devh/boot/grpc/server/security/authentication/CompositeGrpcAuthenticationReader.html) to try multiple readers in order. Your bean definition will look similar to this example: @@ -217,7 +217,7 @@ grpc-server in two ways. #### gRPC security checks One way to secure your application is adding -[`GrpcSecurityMetadataSource`](https://javadoc.io/page/net.devh/grpc-server-spring-boot-autoconfigure/latest/net/devh/boot/grpc/server/security/check/GrpcSecurityMetadataSource.html) +[`GrpcSecurityMetadataSource`](https://javadoc.io/page/net.devh/grpc-server-spring-boot-starter/latest/net/devh/boot/grpc/server/security/check/GrpcSecurityMetadataSource.html) bean to your application context. It allows you to return the security conditions on a per grpc method level. An example bean definition (using hard coded rules) might look like this: @@ -256,7 +256,7 @@ Of course, it is also possible to just use spring-security's annotations. For this use case you have to add the following annotation to one of your `@Configuration` classes: ````java -@EnableGlobalMethodSecurity(___Enabled = true, proxyTargetClass = true) +@EnableMethodSecurity(proxyTargetClass = true) ```` > Please note that `proxyTargetClass = true` is required! If you forget to add it, you will get a lot of `UNIMPLEMENTED` diff --git a/docs/en/trouble-shooting.md b/docs/en/trouble-shooting.md index 2f525ee2c..7396fedca 100644 --- a/docs/en/trouble-shooting.md +++ b/docs/en/trouble-shooting.md @@ -186,7 +186,7 @@ Caused by: java.lang.IllegalStateException: Could not find TLS ALPN provider; no ````txt [...] Caused by: java.lang.IllegalStateException: Failed to create channel: - at net.devh.boot.grpc.client.inject.GrpcClientBeanPostProcessor.processInjectionPoint(GrpcClientBeanPostProcessor.java:118) ~[grpc-client-spring-boot-autoconfigure-2.4.0.RELEASE.jar:2.4.0.RELEASE] + at net.devh.boot.grpc.client.inject.GrpcClientBeanPostProcessor.processInjectionPoint(GrpcClientBeanPostProcessor.java:118) ~[grpc-client-spring-boot-starter-2.4.0.RELEASE.jar:2.4.0.RELEASE] at net.devh.boot.grpc.client.inject.GrpcClientBeanPostProcessor.postProcessBeforeInitialization(GrpcClientBeanPostProcessor.java:77) [...] Caused by: java.lang.IllegalStateException: Could not find TLS ALPN provider; no working netty-tcnative, Conscrypt, or Jetty NPN/ALPN available @@ -283,14 +283,14 @@ grpc.client.__name__.security.trustCertCollection=file:certificates/trusted-serv ````txt Caused by: java.lang.IllegalStateException: Failed to start the grpc server - at net.devh.boot.grpc.server.serverfactory.GrpcServerLifecycle.start(GrpcServerLifecycle.java:51) ~[grpc-server-spring-boot-autoconfigure-2.4.0.RELEASE.jar:2.4.0.RELEASE] + at net.devh.boot.grpc.server.serverfactory.GrpcServerLifecycle.start(GrpcServerLifecycle.java:51) ~[grpc-server-spring-boot-starter-2.4.0.RELEASE.jar:2.4.0.RELEASE] [...] Caused by: java.io.IOException: Failed to bind at io.grpc.netty.shaded.io.grpc.netty.NettyServer.start(NettyServer.java:246) ~[grpc-netty-shaded-1.21.0.jar:1.21.0] at io.grpc.internal.ServerImpl.start(ServerImpl.java:177) ~[grpc-core-1.21.0.jar:1.21.0] at io.grpc.internal.ServerImpl.start(ServerImpl.java:85) ~[grpc-core-1.21.0.jar:1.21.0] - at net.devh.boot.grpc.server.serverfactory.GrpcServerLifecycle.createAndStartGrpcServer(GrpcServerLifecycle.java:90) ~[grpc-server-spring-boot-autoconfigure-2.4.0.RELEASE.jar:2.4.0.RELEASE] - at net.devh.boot.grpc.server.serverfactory.GrpcServerLifecycle.start(GrpcServerLifecycle.java:49) ~[grpc-server-spring-boot-autoconfigure-2.4.0.RELEASE.jar:2.4.0.RELEASE] + at net.devh.boot.grpc.server.serverfactory.GrpcServerLifecycle.createAndStartGrpcServer(GrpcServerLifecycle.java:90) ~[grpc-server-spring-boot-starter-2.4.0.RELEASE.jar:2.4.0.RELEASE] + at net.devh.boot.grpc.server.serverfactory.GrpcServerLifecycle.start(GrpcServerLifecycle.java:49) ~[grpc-server-spring-boot-starter-2.4.0.RELEASE.jar:2.4.0.RELEASE] ... 13 common frames omitted Caused by: java.net.BindException: Address already in use: bind ```` diff --git a/docs/en/versions.md b/docs/en/versions.md index f40511af8..276d1982b 100644 --- a/docs/en/versions.md +++ b/docs/en/versions.md @@ -49,7 +49,8 @@ Current version. | Version | spring-boot | spring-cloud | gRPC | Date | |:-------:|:-----------:|:------------:|:------:|----------:| -| 3.0.0\* | 3.1.4 | 2022.0.4 | 1.58.0 | Nov, 2023 | +| 3.1.0 | 3.2.4 | 2023.0.0 | 1.63.0 | Apr, 2024 | +| 3.0.0 | 3.2.2 | 2023.0.0 | 1.60.1 | Feb, 2024 | (\* Future versions) diff --git a/docs/zh-CN/brave.md b/docs/zh-CN/brave.md index 972e99fcc..1eac559a3 100644 --- a/docs/zh-CN/brave.md +++ b/docs/zh-CN/brave.md @@ -65,7 +65,7 @@ spring.sleuth.grpc.enabled=false ## 附加信息 -Spring-Cloud-Sleuth 提供了一些类,例如[`SpringAwareManagedChannelBuilder`](https://javadoc.io/page/org.springframework.cloud/spring-cloud-sleuth-core/latest/org/springframework/cloud/sleuth/instrument/grpc/SpringAwareManagedChannelBuilder.html),这些类仅仅由于与其他的库兼容而存在。 不要跟那个项目一期使用。 grpc-spring-boot-starter 通过 [`GrpcChannelFactory`](https://javadoc.io/page/net.devh/grpc-client-spring-boot-autoconfigure/latest/net/devh/boot/grpc/client/channelfactory/GrpcChannelFactory.html) 和相关类提供相同 / 扩展的功能提供了开箱即用的能力。 相关阅读 [sleuth's javadoc note](https://github.com/spring-cloud/spring-cloud-sleuth/blob/59216c32f7848ec337fb68d1dbec8e87eeb6bf59/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/grpc/SpringAwareManagedChannelBuilder.java#L31-L34)。 +Spring-Cloud-Sleuth 提供了一些类,例如[`SpringAwareManagedChannelBuilder`](https://javadoc.io/page/org.springframework.cloud/spring-cloud-sleuth-core/latest/org/springframework/cloud/sleuth/instrument/grpc/SpringAwareManagedChannelBuilder.html),这些类仅仅由于与其他的库兼容而存在。 不要跟那个项目一期使用。 grpc-spring-boot-starter 通过 [`GrpcChannelFactory`](https://javadoc.io/page/net.devh/grpc-client-spring-boot-starter/latest/net/devh/boot/grpc/client/channelfactory/GrpcChannelFactory.html) 和相关类提供相同 / 扩展的功能提供了开箱即用的能力。 相关阅读 [sleuth's javadoc note](https://github.com/spring-cloud/spring-cloud-sleuth/blob/59216c32f7848ec337fb68d1dbec8e87eeb6bf59/spring-cloud-sleuth-core/src/main/java/org/springframework/cloud/sleuth/instrument/grpc/SpringAwareManagedChannelBuilder.java#L31-L34)。 ---------- diff --git a/docs/zh-CN/client/configuration.md b/docs/zh-CN/client/configuration.md index cd8ee445d..ed84fcc21 100644 --- a/docs/zh-CN/client/configuration.md +++ b/docs/zh-CN/client/configuration.md @@ -29,11 +29,11 @@ grpc-spring-boot-starter 可以通过 Spring 的 `@ConfigurationProperties` 机 您可以在这里找到所有内置配置属性: -- [`GrpcChannelsProperties`](https://javadoc.io/page/net.devh/grpc-client-spring-boot-autoconfigure/latest/net/devh/boot/grpc/client/config/GrpcChannelsProperties.html) -- [`GrpcChannelProperties`](https://javadoc.io/page/net.devh/grpc-client-spring-boot-autoconfigure/latest/net/devh/boot/grpc/client/config/GrpcChannelProperties.html) -- [`GrpcServerProperties.Security`](https://static.javadoc.io/net.devh/grpc-client-spring-boot-autoconfigure/latest/net/devh/boot/grpc/client/config/GrpcChannelProperties.Security.html) +- [`GrpcChannelsProperties`](https://javadoc.io/page/net.devh/grpc-client-spring-boot-starter/latest/net/devh/boot/grpc/client/config/GrpcChannelsProperties.html) +- [`GrpcChannelProperties`](https://javadoc.io/page/net.devh/grpc-client-spring-boot-starter/latest/net/devh/boot/grpc/client/config/GrpcChannelProperties.html) +- [`GrpcServerProperties.Security`](https://static.javadoc.io/net.devh/grpc-client-spring-boot-starter/latest/net/devh/boot/grpc/client/config/GrpcChannelProperties.Security.html) -如果你希望阅读源代码,你可以查阅 [这里](https://github.com/grpc-ecosystem/grpc-spring/blob/master/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/config/GrpcChannelProperties.java#L58)。 +如果你希望阅读源代码,你可以查阅 [这里](https://github.com/grpc-ecosystem/grpc-spring/blob/master/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/config/GrpcChannelProperties.java#L58)。 Channels 的属性都是以 `grpc.client.__name__.` 或 `grpc.client.__name__.security.` 为前缀。 Channel 的名称从 `@GrpcClient("__name__")` 注解中获取。 如果您想要配置一些其他的选项,如为所有服务端设置可信证书,并可以使用 `GLOBAL` 作为名称。 指定 Channel 的配置项优先于 `GLOBAL` 的配置项 @@ -268,7 +268,7 @@ public StubTransformer call() { > **注意:** 这只能用于在应用程序级别上,而不是应用在每个请求级别上。 -这个库内置提供了一些 `NameResolverProvider`,因此你可以直接使用 [它们](https://github.com/grpc-ecosystem/grpc-spring/tree/master/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/nameresolver)。 +这个库内置提供了一些 `NameResolverProvider`,因此你可以直接使用 [它们](https://github.com/grpc-ecosystem/grpc-spring/tree/master/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/nameresolver)。 你也利用 Java 的 `ServiceLoader` ,在 `META-INF/services/io.grpc.NameResolverProvider` 文件中添加,或者通过在 spring context 中添加,以此注册自定义的 `NameResolverProvider`。 如果你想在你的 `NameResolver` 中使用一些 spring 的 bean, 那么你必须通过 spring 的 context 来定义它 (否则会使用使用 `static`)。 diff --git a/docs/zh-CN/client/getting-started.md b/docs/zh-CN/client/getting-started.md index 1ac18133e..d2aa94531 100644 --- a/docs/zh-CN/client/getting-started.md +++ b/docs/zh-CN/client/getting-started.md @@ -26,7 +26,7 @@ 在我们开始添加依赖关系之前,让我们项目的一些设置建议开始。 -![项目创建](/grpc-spring-boot-starter/assets/images/client-project-setup.svg) +![项目创建](/grpc-spring/assets/images/client-project-setup.svg) 我们建议将您的项目分为2至3个不同的模块。 @@ -98,15 +98,15 @@ buildscript { 以下列表包含您可能在客户端使用到的的所有功能。 如果您不想使用任何高级功能,那么前两个元素可能都是您需要使用的。 -- [`@GrpcClient`](https://javadoc.io/page/net.devh/grpc-client-spring-boot-autoconfigure/latest/net/devh/boot/grpc/client/inject/GrpcClient.html): 在需要注入的客户端的字段或者 setter 方法上这个注解。 支持 `Channel`和各种类型的 `Stub`。 请不要将 `@GrpcClient` 与 `@Autowireed` 或 `@Inject` 一起使用。 目前,它不支持构造函数和 `@Bean` 方法参数。 这种情况请查看后面 `@GrpcClientBean` 的使用文档。 **注意:** 同一应用程序提供的服务只能在 ` ApplicationStartedEvent` 之后访问/调用。 连接到外部服务的 Stubs 可以提前使用;从 `@PostConstruct` / `初始化Bean#afterPropertiesSet()` 开始。 -- [`@GrpcClientBean`](https://javadoc.io/page/net.devh/grpc-client-spring-boot-autoconfigure/latest/net/devh/boot/grpc/client/inject/GrpcClientBean.html): 注解会把使用 `@GrpcClient` 注解的类型注册到 Spring Context 中,方便 `@Autowired` 和 `@Qualifier` 的使用。 这个注解可以重复添加到您的 `@Configuration` 类中。 +- [`@GrpcClient`](https://javadoc.io/page/net.devh/grpc-client-spring-boot-starter/latest/net/devh/boot/grpc/client/inject/GrpcClient.html): 在需要注入的客户端的字段或者 setter 方法上这个注解。 支持 `Channel`和各种类型的 `Stub`。 请不要将 `@GrpcClient` 与 `@Autowireed` 或 `@Inject` 一起使用。 目前,它不支持构造函数和 `@Bean` 方法参数。 这种情况请查看后面 `@GrpcClientBean` 的使用文档。 **注意:** 同一应用程序提供的服务只能在 ` ApplicationStartedEvent` 之后访问/调用。 连接到外部服务的 Stubs 可以提前使用;从 `@PostConstruct` / `初始化Bean#afterPropertiesSet()` 开始。 +- [`@GrpcClientBean`](https://javadoc.io/page/net.devh/grpc-client-spring-boot-starter/latest/net/devh/boot/grpc/client/inject/GrpcClientBean.html): 注解会把使用 `@GrpcClient` 注解的类型注册到 Spring Context 中,方便 `@Autowired` 和 `@Qualifier` 的使用。 这个注解可以重复添加到您的 `@Configuration` 类中。 - [`Channel`](https://javadoc.io/page/io.grpc/grpc-all/latest/io/grpc/Channel.html): Channel 是单个地址的连接池。 目标服务器可能是多个 gRPC 服务。 该地址将使用 `NameResolver` 做解析,最终它可能会指向一批固定数量或动态数量的服务端。 - [`ManagedChannel`](https://javadoc.io/page/io.grpc/grpc-all/latest/io/grpc/ManagedChannel.html): ManagedChannel 是 Channel 的一种特殊变体,因为它允许对连接池进行管理操作,例如将其关闭。 - [`NameResolver`](https://javadoc.io/page/io.grpc/grpc-all/latest/io/grpc/NameResolver.html)、 [`NameResolver.Factory`](https://javadoc.io/page/io.grpc/grpc-all/latest/io/grpc/NameResolver.Factory.html): 一个用于将地址解析为`SocketAddress` 列表的类 ,当与先前列出的服务端连表连接失败或通道空闲时,地址将会重新做解析。 参阅 [Configuration -> Choosing the Target](configuration.md#choosing-the-target)。 -- [`ClientInterceptor`](https://javadoc.io/page/io.grpc/grpc-all/latest/io/grpc/ClientInterceptor.html): 在每个 `Channel` 处理之前拦截它们。 可以用于日志、监测、元数据处理和请求/响应的重写。 grpc-spring-boot-starter 将自动接收所有带有 [`@GrpcGlobalClientInterceptor`](https://javadoc.io/page/net.devh/grpc-client-spring-boot-autoconfigure/latest/net/devh/boot/grpc/client/interceptor/GrpcGlobalClientInterceptor.html) 注解以及手动注册在[`GlobalClientInterceptorRegistry`](https://javadoc.io/page/net.devh/grpc-client-spring-boot-autoconfigure/latest/net/devh/boot/grpc/client/interceptor/GlobalClientInterceptorRegistry.html) 上的客户拦截器。 参阅 [Configuration -> ClientInterceptor](configuration.md#clientinterceptor)。 -- [`CallCredentials`](https://javadoc.io/page/io.grpc/grpc-all/latest/io/grpc/CallCredentials.html): 管理身份验证的组件。 它可以用于存储凭据和会话令牌。 它还可以用来身份验证,并且使用返回的令牌(例如 OAuth) 来授权实际请求。 除此之外,如果令牌过期并且重新发送请求,它可以续签令牌。 如果您的应用程序上下文中只存在一个 `CallCredentials` bean,那么 spring 将会自动将其附加到`Stub`( **非** `Channel` )。 [`CallCredentialsHelper`](https://javadoc.io/page/net.devh/grpc-client-spring-boot-autoconfigure/latest/net/devh/boot/grpc/client/security/CallCredentialsHelper.html)工具类可以帮助您创建常用的 `CallCredentials` 类型和相关的`StubTransformer`。 -- [`StubFactory`](https://javadoc.io/page/net.devh/grpc-client-spring-boot-autoconfigure/latest/net/devh/boot/grpc/client/stubfactory/StubFactory.html): 一个用于从 `Channel` 创建特定 `Stub` 的工厂。 可以注册多个 `StubFactory`,以支持不同类型的 stub。 参阅 [Configuration -> StubFactory](configuration.md#stubfactory)。 -- [`StubTransformer`](https://javadoc.io/page/net.devh/grpc-client-spring-boot-autoconfigure/latest/net/devh/boot/grpc/client/inject/StubTransformer.html): 所有客户端的 `Stub` 的注入之前应用的转换器。 参阅 [Configuration -> StubTransformer](configuration.md#stubtransformer)。 +- [`ClientInterceptor`](https://javadoc.io/page/io.grpc/grpc-all/latest/io/grpc/ClientInterceptor.html): 在每个 `Channel` 处理之前拦截它们。 可以用于日志、监测、元数据处理和请求/响应的重写。 grpc-spring-boot-starter 将自动接收所有带有 [`@GrpcGlobalClientInterceptor`](https://javadoc.io/page/net.devh/grpc-client-spring-boot-starter/latest/net/devh/boot/grpc/client/interceptor/GrpcGlobalClientInterceptor.html) 注解以及手动注册在[`GlobalClientInterceptorRegistry`](https://javadoc.io/page/net.devh/grpc-client-spring-boot-starter/latest/net/devh/boot/grpc/client/interceptor/GlobalClientInterceptorRegistry.html) 上的客户拦截器。 参阅 [Configuration -> ClientInterceptor](configuration.md#clientinterceptor)。 +- [`CallCredentials`](https://javadoc.io/page/io.grpc/grpc-all/latest/io/grpc/CallCredentials.html): 管理身份验证的组件。 它可以用于存储凭据和会话令牌。 它还可以用来身份验证,并且使用返回的令牌(例如 OAuth) 来授权实际请求。 除此之外,如果令牌过期并且重新发送请求,它可以续签令牌。 如果您的应用程序上下文中只存在一个 `CallCredentials` bean,那么 spring 将会自动将其附加到`Stub`( **非** `Channel` )。 [`CallCredentialsHelper`](https://javadoc.io/page/net.devh/grpc-client-spring-boot-starter/latest/net/devh/boot/grpc/client/security/CallCredentialsHelper.html)工具类可以帮助您创建常用的 `CallCredentials` 类型和相关的`StubTransformer`。 +- [`StubFactory`](https://javadoc.io/page/net.devh/grpc-client-spring-boot-starter/latest/net/devh/boot/grpc/client/stubfactory/StubFactory.html): 一个用于从 `Channel` 创建特定 `Stub` 的工厂。 可以注册多个 `StubFactory`,以支持不同类型的 stub。 参阅 [Configuration -> StubFactory](configuration.md#stubfactory)。 +- [`StubTransformer`](https://javadoc.io/page/net.devh/grpc-client-spring-boot-starter/latest/net/devh/boot/grpc/client/inject/StubTransformer.html): 所有客户端的 `Stub` 的注入之前应用的转换器。 参阅 [Configuration -> StubTransformer](configuration.md#stubtransformer)。 ### 访问客户端 diff --git a/docs/zh-CN/client/security.md b/docs/zh-CN/client/security.md index c6a953c00..6a47112a7 100644 --- a/docs/zh-CN/client/security.md +++ b/docs/zh-CN/client/security.md @@ -113,7 +113,7 @@ CallCredentials bearerAuthForwardingCredentials() { ### 使用 CallCredentials -如果您的应用程序上下文中只有一个`CallCredentials`,我们将自动为您创建一个 `StubTransformer`,并配置到所有的 `Stub`上。 如果您想为每个 Stub 配置不同的凭据,那么您可以使用 [`CallCredentialsHelper`](https://javadoc.io/page/net.devh/grpc-client-spring-boot-autoconfigure/latest/net/devh/boot/grpc/client/security/CallCredentialsHelper.html) 中提供的帮助方法。 +如果您的应用程序上下文中只有一个`CallCredentials`,我们将自动为您创建一个 `StubTransformer`,并配置到所有的 `Stub`上。 如果您想为每个 Stub 配置不同的凭据,那么您可以使用 [`CallCredentialsHelper`](https://javadoc.io/page/net.devh/grpc-client-spring-boot-starter/latest/net/devh/boot/grpc/client/security/CallCredentialsHelper.html) 中提供的帮助方法。 > **注意:** `StubTransformer` 只能自动配置注入的 `Stub`。 它们无法修改原始的 `Channel`。 diff --git a/docs/zh-CN/server/configuration.md b/docs/zh-CN/server/configuration.md index 47679f21e..90ae0a02a 100644 --- a/docs/zh-CN/server/configuration.md +++ b/docs/zh-CN/server/configuration.md @@ -29,10 +29,10 @@ grpc-spring-boot-starter 可以通过 Spring 的 `@ConfigurationProperties` 机 您可以在这里找到所有内置配置属性: -- [`GrpcServerProperties`](https://javadoc.io/page/net.devh/grpc-server-spring-boot-autoconfigure/latest/net/devh/boot/grpc/server/config/GrpcServerProperties.html) -- [`GrpcServerProperties.Security`](https://javadoc.io/page/net.devh/grpc-server-spring-boot-autoconfigure/latest/net/devh/boot/grpc/server/config/GrpcServerProperties.Security.html) +- [`GrpcServerProperties`](https://javadoc.io/page/net.devh/grpc-server-spring-boot-starter/latest/net/devh/boot/grpc/server/config/GrpcServerProperties.html) +- [`GrpcServerProperties.Security`](https://javadoc.io/page/net.devh/grpc-server-spring-boot-starter/latest/net/devh/boot/grpc/server/config/GrpcServerProperties.Security.html) -如果你希望阅读源代码,你可以查阅 [这里](https://github.com/grpc-ecosystem/grpc-spring/blob/master/grpc-server-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/server/config/GrpcServerProperties.java#L50)。 +如果你希望阅读源代码,你可以查阅 [这里](https://github.com/grpc-ecosystem/grpc-spring/blob/master/grpc-server-spring-boot-starter/src/main/java/net/devh/boot/grpc/server/config/GrpcServerProperties.java#L50)。 Channels 的属性都是以 `grpc.server..` 或 `grpc.client..security.` 为前缀。 diff --git a/docs/zh-CN/server/getting-started.md b/docs/zh-CN/server/getting-started.md index 20bcb53ec..48bfb0d6b 100644 --- a/docs/zh-CN/server/getting-started.md +++ b/docs/zh-CN/server/getting-started.md @@ -28,7 +28,7 @@ 在我们开始添加依赖关系之前,让我们项目的一些设置建议开始。 -![项目创建](/grpc-spring-boot-starter/assets/images/server-project-setup.svg) +![项目创建](/grpc-spring/assets/images/server-project-setup.svg) 我们建议将您的项目分为2至3个不同的模块。 diff --git a/docs/zh-CN/server/security.md b/docs/zh-CN/server/security.md index b29bde9c9..92649d260 100644 --- a/docs/zh-CN/server/security.md +++ b/docs/zh-CN/server/security.md @@ -98,15 +98,15 @@ cat client*.crt > trusted-clients.crt.collection ### 配置身份验证 -为了支持来自 grpc 客户端的身份验证,您必须定义客户端如何被允许进行身份验证。 您可以通过自定义 [`GrpcAuthenticationReader`](https://javadoc.io/page/net.devh/grpc-server-spring-boot-autoconfigure/latest/net/devh/boot/grpc/server/security/authentication/GrpcAuthenticationReader.html) 来实现。 +为了支持来自 grpc 客户端的身份验证,您必须定义客户端如何被允许进行身份验证。 您可以通过自定义 [`GrpcAuthenticationReader`](https://javadoc.io/page/net.devh/grpc-server-spring-boot-starter/latest/net/devh/boot/grpc/server/security/authentication/GrpcAuthenticationReader.html) 来实现。 grpc-spring-boot-starter 提供了一些内置实现: -- [`AnonymousAuthenticationReader`](https://javadoc.io/page/net.devh/grpc-server-spring-boot-autoconfigure/latest/net/devh/boot/grpc/server/security/authentication/AnonymousAuthenticationReader.html) Spring 的匿名身份认证。 -- [`BasicGrpcAuthenticationReader`](https://javadoc.io/page/net.devh/grpc-server-spring-boot-autoconfigure/latest/net/devh/boot/grpc/server/security/authentication/BasicGrpcAuthenticationReader.html) 基础身份认证。 -- [`BearerAuthenticationReader`](https://javadoc.io/page/net.devh/grpc-server-spring-boot-autoconfigure/latest/net/devh/boot/grpc/server/security/authentication/BearerAuthenticationReader.html) OAuth 以及类似协议的身份认证。 -- [`SSLContextGrpcAuthenticationReader`](https://javadoc.io/page/net.devh/grpc-server-spring-boot-autoconfigure/latest/net/devh/boot/grpc/server/security/authentication/SSLContextGrpcAuthenticationReader.html) 基于证书的身份认证。 -- [`CompositeGrpcAuthenticationReader`](https://javadoc.io/page/net.devh/grpc-server-spring-boot-autoconfigure/latest/net/devh/boot/grpc/server/security/authentication/CompositeGrpcAuthenticationReader.html) 依次尝试多个身份验证器。 +- [`AnonymousAuthenticationReader`](https://javadoc.io/page/net.devh/grpc-server-spring-boot-starter/latest/net/devh/boot/grpc/server/security/authentication/AnonymousAuthenticationReader.html) Spring 的匿名身份认证。 +- [`BasicGrpcAuthenticationReader`](https://javadoc.io/page/net.devh/grpc-server-spring-boot-starter/latest/net/devh/boot/grpc/server/security/authentication/BasicGrpcAuthenticationReader.html) 基础身份认证。 +- [`BearerAuthenticationReader`](https://javadoc.io/page/net.devh/grpc-server-spring-boot-starter/latest/net/devh/boot/grpc/server/security/authentication/BearerAuthenticationReader.html) OAuth 以及类似协议的身份认证。 +- [`SSLContextGrpcAuthenticationReader`](https://javadoc.io/page/net.devh/grpc-server-spring-boot-starter/latest/net/devh/boot/grpc/server/security/authentication/SSLContextGrpcAuthenticationReader.html) 基于证书的身份认证。 +- [`CompositeGrpcAuthenticationReader`](https://javadoc.io/page/net.devh/grpc-server-spring-boot-starter/latest/net/devh/boot/grpc/server/security/authentication/CompositeGrpcAuthenticationReader.html) 依次尝试多个身份验证器。 您 Bean 的定义将跟下面这个示例类似: @@ -190,7 +190,7 @@ GrpcAuthenticationReader authenticationReader() { #### gRPC 安全检查 -保护应用程序安全的一种方式是将 [`GrpcSecurityMetadataSource`](https://javadoc.io/page/net.devh/grpc-server-spring-boot-autoconfigure/latest/net/devh/boot/grpc/server/security/check/GrpcSecurityMetadataSource.html) bean 添加到您的应用商家文中。 它允许您在每个 grpc 方法级别返回安全条件。 +保护应用程序安全的一种方式是将 [`GrpcSecurityMetadataSource`](https://javadoc.io/page/net.devh/grpc-server-spring-boot-starter/latest/net/devh/boot/grpc/server/security/check/GrpcSecurityMetadataSource.html) bean 添加到您的应用商家文中。 它允许您在每个 grpc 方法级别返回安全条件。 一个示例 bean 定义 (使用硬代码规则) 可能如下所示: diff --git a/docs/zh-CN/trouble-shooting.md b/docs/zh-CN/trouble-shooting.md index 3259ef68d..86da257a3 100644 --- a/docs/zh-CN/trouble-shooting.md +++ b/docs/zh-CN/trouble-shooting.md @@ -158,7 +158,7 @@ Caused by: java.lang.IllegalStateException: Could not find TLS ALPN provider; no ````txt [...] Caused by: java.lang.IllegalStateException: Failed to create channel: - at net.devh.boot.grpc.client.inject.GrpcClientBeanPostProcessor.processInjectionPoint(GrpcClientBeanPostProcessor.java:118) ~[grpc-client-spring-boot-autoconfigure-2.4.0.RELEASE.jar:2.4.0.RELEASE] + at net.devh.boot.grpc.client.inject.GrpcClientBeanPostProcessor.processInjectionPoint(GrpcClientBeanPostProcessor.java:118) ~[grpc-client-spring-boot-starter-2.4.0.RELEASE.jar:2.4.0.RELEASE] at net.devh.boot.grpc.client.inject.GrpcClientBeanPostProcessor.postProcessBeforeInitialization(GrpcClientBeanPostProcessor.java:77) [...] Caused by: java.lang.IllegalStateException: Could not find TLS ALPN provider; no working netty-tcnative, Conscrypt, or Jetty NPN/ALPN available @@ -252,14 +252,14 @@ grpc.client.__name__.security.trustCertCollection=file:certificates/trusted-serv ````txt Caused by: java.lang.IllegalStateException: Failed to start the grpc server - at net.devh.boot.grpc.server.serverfactory.GrpcServerLifecycle.start(GrpcServerLifecycle.java:51) ~[grpc-server-spring-boot-autoconfigure-2.4.0.RELEASE.jar:2.4.0.RELEASE] + at net.devh.boot.grpc.server.serverfactory.GrpcServerLifecycle.start(GrpcServerLifecycle.java:51) ~[grpc-server-spring-boot-starter-2.4.0.RELEASE.jar:2.4.0.RELEASE] [...] Caused by: java.io.IOException: Failed to bind at io.grpc.netty.shaded.io.grpc.netty.NettyServer.start(NettyServer.java:246) ~[grpc-netty-shaded-1.21.0.jar:1.21.0] at io.grpc.internal.ServerImpl.start(ServerImpl.java:177) ~[grpc-core-1.21.0.jar:1.21.0] at io.grpc.internal.ServerImpl.start(ServerImpl.java:85) ~[grpc-core-1.21.0.jar:1.21.0] - at net.devh.boot.grpc.server.serverfactory.GrpcServerLifecycle.createAndStartGrpcServer(GrpcServerLifecycle.java:90) ~[grpc-server-spring-boot-autoconfigure-2.4.0.RELEASE.jar:2.4.0.RELEASE] - at net.devh.boot.grpc.server.serverfactory.GrpcServerLifecycle.start(GrpcServerLifecycle.java:49) ~[grpc-server-spring-boot-autoconfigure-2.4.0.RELEASE.jar:2.4.0.RELEASE] + at net.devh.boot.grpc.server.serverfactory.GrpcServerLifecycle.createAndStartGrpcServer(GrpcServerLifecycle.java:90) ~[grpc-server-spring-boot-starter-2.4.0.RELEASE.jar:2.4.0.RELEASE] + at net.devh.boot.grpc.server.serverfactory.GrpcServerLifecycle.start(GrpcServerLifecycle.java:49) ~[grpc-server-spring-boot-starter-2.4.0.RELEASE.jar:2.4.0.RELEASE] ... 13 common frames omitted Caused by: java.net.BindException: Address already in use: bind ```` diff --git a/docs/zh-CN/versions.md b/docs/zh-CN/versions.md index 5bb0e66aa..166a913f8 100644 --- a/docs/zh-CN/versions.md +++ b/docs/zh-CN/versions.md @@ -38,9 +38,10 @@ 当前版本。 -| 版本 | spring-boot | spring-cloud | gRPC | 日期 | -|:------:|:-----------:|:------------:|:------:|---------:| -| 3.0.0* | 3.1.4 | 2022.0.4 | 1.58.0 | 2023年9月 | +| 版本 | spring-boot | spring-cloud | gRPC | 日期 | +|:-----:|:-----------:|:------------:|:-------:|--------:| +| 3.1.0 | 3.2.4 | 2023.0.0 | 1.63.0 | 2024年4月 | +| 3.0.0 | 3.2.2 | 2023.0.0 | 1.60.1 | 2024年2月 | (\* 未来的版本) diff --git a/examples/grpc-observability/README.md b/examples/grpc-observability/README.md new file mode 100644 index 000000000..0b652289e --- /dev/null +++ b/examples/grpc-observability/README.md @@ -0,0 +1,132 @@ +# gRPC-Observability Example + +## Features + +* End to end examples to generate the gRPC metrics with Prometheus, including: + * A frontend microservice ( + in [gRPC-Spring](https://github.com/grpc-ecosystem/grpc-spring)), which + keeps calling the backend microservices. + * A backend microservice ( + in [gRPC-Spring](https://github.com/grpc-ecosystem/grpc-spring)), which + serves unary/client streaming/server streaming and bidi streaming example + services. + * Instructions to containerize them and deploy them in kubernetes +* Monitoring dashboards to support + the [gRPC A66](https://github.com/grpc/proposal/blob/master/A66-otel-stats.md) + spec. +* Deploy the Grafana and import + the [gRPC A66](https://github.com/grpc/proposal/blob/master/A66-otel-stats.md) + dashboard + +## Build the end to end example + +Under the root directory of your grpc-spring repo + +``` +./gradlew build +``` + +## Run the end to end example + +Run the backend microservice locally + +``` +./gradlew examples:grpc-observability:backend:bootRun +``` + +Run the frontend microservice locally. Please note that the backend needs to be +started first to be ready to serve the client calls. + +``` +./gradlew examples:grpc-observability:frontend:bootRun +``` + +The backend microservice will + +- listen on the TCP port 9091 for the gRPC calls from the frontend microservice. +- listen on the TCP port 8081 for the Prometheus scrape requests. + +The frontend microservice will + +- send the unary/client streaming/server streaming/bidi streaming calls to the + backend microservices via TCP port 9091. +- listen on the TCP port 8080 for the Prometheus scrape requests. + +## Containerize the frontend/backend microservices + +Build the docker image for the backend microservice + +``` +docker build -t grpc-observability/grpc-spring-example-backend examples/grpc-observability/backend +``` + +Run the backend microservice with docker + +``` +docker run --network host grpc-observability/grpc-spring-example-backend +``` + +Build the docker image for the frontend microservice + +``` +docker build -t grpc-observability/grpc-spring-example-frontend examples/grpc-observability/frontend +``` + +Run the frontend microservice with docker + +``` +docker run --network host grpc-observability/grpc-spring-example-frontend +``` + +## Deploy the end to end example to kubernetes + +To deploy the example to kubernetes, please upload the docker images to a +registry by yourself and modify the frontend.yaml/backend.yaml with your docker +image location, and then run following commands. + +Deploy the backend microservice in kubernetes + +``` +kubectl apply -f ./examples/grpc-observability/backend/backend.yaml +``` + +Deploy the frontend microservice in kubernetes + +``` +kubectl apply -f ./examples/grpc-observability/frontend/frontend.yaml +``` + +## Set up the Prometheus to collect the gRPC metrics + +Once the frontend/backend microservices are deployed (either locally or on +cloud), you may set up the Prometheus to start scraping the metrics from them. +Depends on where you run the frontend/backend microservices, you may need to +deploy the Prometheus to a proper location to be able to access them, such as, +the same cloud, etc. + +If you +use [Google Managed Prometheus](https://cloud.google.com/stackdriver/docs/managed-prometheus), +you may need to configure the PodMonitoring resource to tell where are endpoints +to scrape the Prometheus metrics. + +``` +kubectl apply -f ./examples/grpc-observability/pod_monitoring.yaml +``` + +## Set up the Grafana dashboard + +Once we have the gRPC metrics scraped by the Prometheus, we may set up a Grafana +server to visualize them. + +- Set up a Grafana server, deploy it to a place where can connect the Prometheus + server as its data source. +- Create a Grafana dashboard by importing the + [examples/grpc-observability/grafana/prometheus/microservices-grpc-dashboard.json](http://github.com/grpc-ecosystem/grpc-spring/blob/master/examples/grpc-observability/grafana/prometheus/microservices-grpc-dashboard.json) + file. + +If you +use [Google Managed Prometheus](https://cloud.google.com/stackdriver/docs/managed-prometheus), +you need to follow +the [Grafana Query User Guide](https://cloud.google.com/stackdriver/docs/managed-prometheus/query) +to set up the Grafana server +and [data source syncer](https://github.com/GoogleCloudPlatform/prometheus-engine/tree/main/cmd/datasource-syncer). diff --git a/examples/grpc-observability/backend/Dockerfile b/examples/grpc-observability/backend/Dockerfile new file mode 100644 index 000000000..63fadd654 --- /dev/null +++ b/examples/grpc-observability/backend/Dockerfile @@ -0,0 +1,3 @@ +FROM eclipse-temurin:17-jdk-alpine +COPY build/libs/backend.jar backend.jar +ENTRYPOINT ["java","-jar","/backend.jar"] diff --git a/examples/grpc-observability/backend/backend.yaml b/examples/grpc-observability/backend/backend.yaml new file mode 100644 index 000000000..cdc582398 --- /dev/null +++ b/examples/grpc-observability/backend/backend.yaml @@ -0,0 +1,52 @@ +# Copyright 2023 gRPC Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +apiVersion: apps/v1 +kind: Deployment +metadata: + name: backend +spec: + replicas: 1 + selector: + matchLabels: + app: backend + template: + metadata: + labels: + app: backend + monitor: prometheus + spec: + containers: + - name: backend + # Please upload the docker image of grpc-observability/grpc-spring-example-backend to an image registry, such as https://cloud.google.com/artifact-registry. + image: + ports: + - name: monitoring + containerPort: 8081 + - name: grpc + containerPort: 9091 +--- +apiVersion: v1 +kind: Service +metadata: + name: backend +spec: + clusterIP: None + selector: + app: backend + ports: + - name: monitoring + port: 8081 + - name: grpc + port: 9091 diff --git a/examples/grpc-observability/backend/build.gradle b/examples/grpc-observability/backend/build.gradle new file mode 100644 index 000000000..9d449ef3c --- /dev/null +++ b/examples/grpc-observability/backend/build.gradle @@ -0,0 +1,26 @@ +// Copyright 2023 The gRPC-GCP-Mobile Authors +// All rights reserved. +// +// 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. +// +plugins { + id 'org.springframework.boot' +} + +dependencies { + implementation project(':examples:grpc-observability:proto') + implementation project(':grpc-server-spring-boot-starter') + implementation 'org.springframework.boot:spring-boot-starter-actuator' + implementation "org.springframework.boot:spring-boot-starter-web" + implementation 'io.micrometer:micrometer-registry-prometheus' +} diff --git a/examples/grpc-observability/backend/src/main/java/net/devh/boot/grpc/examples/observability/backend/BackendApplication.java b/examples/grpc-observability/backend/src/main/java/net/devh/boot/grpc/examples/observability/backend/BackendApplication.java new file mode 100644 index 000000000..a9948b71c --- /dev/null +++ b/examples/grpc-observability/backend/src/main/java/net/devh/boot/grpc/examples/observability/backend/BackendApplication.java @@ -0,0 +1,29 @@ +/* + * 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.examples.observability.backend; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class BackendApplication { + + public static void main(String[] args) { + SpringApplication.run(BackendApplication.class, args); + } + +} diff --git a/examples/grpc-observability/backend/src/main/java/net/devh/boot/grpc/examples/observability/backend/ExampleServiceImpl.java b/examples/grpc-observability/backend/src/main/java/net/devh/boot/grpc/examples/observability/backend/ExampleServiceImpl.java new file mode 100644 index 000000000..64b6f6d33 --- /dev/null +++ b/examples/grpc-observability/backend/src/main/java/net/devh/boot/grpc/examples/observability/backend/ExampleServiceImpl.java @@ -0,0 +1,116 @@ +/* + * 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.examples.observability.backend; + +import java.util.concurrent.ThreadLocalRandom; + +import io.grpc.Status; +import io.grpc.stub.StreamObserver; +import net.devh.boot.grpc.examples.observability.proto.BidiStreamingRequest; +import net.devh.boot.grpc.examples.observability.proto.BidiStreamingResponse; +import net.devh.boot.grpc.examples.observability.proto.ClientStreamingRequest; +import net.devh.boot.grpc.examples.observability.proto.ClientStreamingResponse; +import net.devh.boot.grpc.examples.observability.proto.ExampleServiceGrpc.ExampleServiceImplBase; +import net.devh.boot.grpc.examples.observability.proto.ServerStreamingRequest; +import net.devh.boot.grpc.examples.observability.proto.ServerStreamingResponse; +import net.devh.boot.grpc.examples.observability.proto.UnaryRequest; +import net.devh.boot.grpc.examples.observability.proto.UnaryResponse; +import net.devh.boot.grpc.server.service.GrpcService; + +@GrpcService +public class ExampleServiceImpl extends ExampleServiceImplBase { + + private boolean InjectError() { + // We create ~5% error. + return ThreadLocalRandom.current().nextInt(0, 99) >= 95; + } + + @Override + public void unaryRpc(UnaryRequest request, + StreamObserver responseObserver) { + if (InjectError()) { + responseObserver.onError(Status.INTERNAL.asException()); + } else { + responseObserver.onNext(UnaryResponse.newBuilder().setMessage(request.getMessage()).build()); + responseObserver.onCompleted(); + } + } + + @Override + public StreamObserver clientStreamingRpc( + StreamObserver responseObserver) { + return new StreamObserver<>() { + @Override + public void onNext(ClientStreamingRequest value) { + responseObserver.onNext( + ClientStreamingResponse.newBuilder().setMessage(value.getMessage()).build()); + } + + @Override + public void onError(Throwable t) { + responseObserver.onError(t); + } + + @Override + public void onCompleted() { + if (InjectError()) { + responseObserver.onError(Status.INTERNAL.asException()); + } else { + responseObserver.onCompleted(); + } + } + }; + } + + @Override + public void serverStreamingRpc(ServerStreamingRequest request, + StreamObserver responseObserver) { + if (InjectError()) { + responseObserver.onError(Status.INTERNAL.asException()); + } else { + responseObserver.onNext( + ServerStreamingResponse.newBuilder().setMessage(request.getMessage()).build()); + responseObserver.onCompleted(); + } + } + + @Override + public StreamObserver bidiStreamingRpc( + StreamObserver responseObserver) { + return new StreamObserver<>() { + @Override + public void onNext(BidiStreamingRequest value) { + responseObserver.onNext( + BidiStreamingResponse.newBuilder().setMessage(value.getMessage()).build()); + } + + @Override + public void onError(Throwable t) { + responseObserver.onError(t); + } + + @Override + public void onCompleted() { + if (InjectError()) { + responseObserver.onError(Status.INTERNAL.asException()); + } else { + responseObserver.onCompleted(); + } + } + }; + } +} diff --git a/examples/grpc-observability/backend/src/main/resources/application.properties b/examples/grpc-observability/backend/src/main/resources/application.properties new file mode 100644 index 000000000..588d04cfd --- /dev/null +++ b/examples/grpc-observability/backend/src/main/resources/application.properties @@ -0,0 +1,7 @@ +# Port serves the monitoring traffic. +server.port=8081 +# Expose the prometheus metrics via the monitoring port. +# By default, expose on `/actuator/prometheus`. +management.endpoints.web.exposure.include=prometheus +# Port serves the gRPC traffic. +grpc.server.port=9091 diff --git a/examples/grpc-observability/frontend/Dockerfile b/examples/grpc-observability/frontend/Dockerfile new file mode 100644 index 000000000..94fa7ead2 --- /dev/null +++ b/examples/grpc-observability/frontend/Dockerfile @@ -0,0 +1,3 @@ +FROM eclipse-temurin:17-jdk-alpine +COPY build/libs/frontend.jar frontend.jar +ENTRYPOINT ["java","-jar","/frontend.jar"] diff --git a/examples/grpc-observability/frontend/build.gradle b/examples/grpc-observability/frontend/build.gradle new file mode 100644 index 000000000..5d46c11e3 --- /dev/null +++ b/examples/grpc-observability/frontend/build.gradle @@ -0,0 +1,26 @@ +// Copyright 2023 The gRPC-GCP-Mobile Authors +// All rights reserved. +// +// 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. +// +plugins { + id 'org.springframework.boot' +} + +dependencies { + implementation project(':examples:grpc-observability:proto') + implementation project(':grpc-client-spring-boot-starter') + implementation 'org.springframework.boot:spring-boot-starter-actuator' + implementation "org.springframework.boot:spring-boot-starter-web" + implementation 'io.micrometer:micrometer-registry-prometheus' +} diff --git a/examples/grpc-observability/frontend/frontend.yaml b/examples/grpc-observability/frontend/frontend.yaml new file mode 100644 index 000000000..914441c12 --- /dev/null +++ b/examples/grpc-observability/frontend/frontend.yaml @@ -0,0 +1,49 @@ +# Copyright 2023 gRPC Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +apiVersion: apps/v1 +kind: Deployment +metadata: + name: frontend +spec: + replicas: 1 + selector: + matchLabels: + app: frontend + template: + metadata: + labels: + app: frontend + monitor: prometheus + spec: + containers: + - name: frontend + # Please upload the docker image of grpc-observability/grpc-spring-example-frontend to an image registry, such as https://cloud.google.com/artifact-registry. + image: + imagePullPolicy: Always + ports: + - name: monitoring + containerPort: 8080 +--- +apiVersion: v1 +kind: Service +metadata: + name: frontend +spec: + clusterIP: None + selector: + app: frontend + ports: + - name: monitoring + port: 8080 diff --git a/examples/grpc-observability/frontend/src/main/java/net/devh/boot/grpc/examples/observability/frontend/FrontendApplication.java b/examples/grpc-observability/frontend/src/main/java/net/devh/boot/grpc/examples/observability/frontend/FrontendApplication.java new file mode 100644 index 000000000..190e93d90 --- /dev/null +++ b/examples/grpc-observability/frontend/src/main/java/net/devh/boot/grpc/examples/observability/frontend/FrontendApplication.java @@ -0,0 +1,148 @@ +/* + * 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.examples.observability.frontend; + +import java.util.concurrent.ThreadLocalRandom; +import java.util.logging.Logger; + +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +import io.grpc.Status; +import io.grpc.stub.StreamObserver; +import net.devh.boot.grpc.client.inject.GrpcClient; +import net.devh.boot.grpc.examples.observability.proto.BidiStreamingRequest; +import net.devh.boot.grpc.examples.observability.proto.BidiStreamingResponse; +import net.devh.boot.grpc.examples.observability.proto.ClientStreamingRequest; +import net.devh.boot.grpc.examples.observability.proto.ClientStreamingResponse; +import net.devh.boot.grpc.examples.observability.proto.ExampleServiceGrpc.ExampleServiceStub; +import net.devh.boot.grpc.examples.observability.proto.ServerStreamingRequest; +import net.devh.boot.grpc.examples.observability.proto.ServerStreamingResponse; +import net.devh.boot.grpc.examples.observability.proto.UnaryRequest; +import net.devh.boot.grpc.examples.observability.proto.UnaryResponse; + +@SpringBootApplication +public class FrontendApplication implements CommandLineRunner { + + private static final Logger LOGGER = Logger.getLogger(FrontendApplication.class.getName()); + + public static void main(String[] args) { + SpringApplication.run(FrontendApplication.class, args); + } + + @GrpcClient("backend") + private ExampleServiceStub stub; + + private void CallUnaryRpc() { + byte[] bytes = new byte[ThreadLocalRandom.current().nextInt(10240, 20480)]; + ThreadLocalRandom.current().nextBytes(bytes); + UnaryRequest request = UnaryRequest.newBuilder().setMessage(new String(bytes)).build(); + stub.unaryRpc(request, new StreamObserver<>() { + @Override + public void onNext(UnaryResponse value) {} + + @Override + public void onError(Throwable t) { + LOGGER.severe(Status.fromThrowable(t).toString()); + CallUnaryRpc(); + } + + @Override + public void onCompleted() { + CallUnaryRpc(); + } + }); + } + + private void CallClientStreamingRpc() { + byte[] bytes = new byte[ThreadLocalRandom.current().nextInt(10240, 20480)]; + ThreadLocalRandom.current().nextBytes(bytes); + ClientStreamingRequest request = ClientStreamingRequest.newBuilder() + .setMessage(new String(bytes)).build(); + StreamObserver requestStreamObserver = stub.clientStreamingRpc( + new StreamObserver<>() { + @Override + public void onNext(ClientStreamingResponse value) {} + + @Override + public void onError(Throwable t) { + CallClientStreamingRpc(); + } + + @Override + public void onCompleted() { + CallClientStreamingRpc(); + } + }); + requestStreamObserver.onNext(request); + requestStreamObserver.onCompleted(); + } + + private void CallServerStreamingRpc() { + byte[] bytes = new byte[ThreadLocalRandom.current().nextInt(10240, 20480)]; + ThreadLocalRandom.current().nextBytes(bytes); + ServerStreamingRequest request = ServerStreamingRequest.newBuilder() + .setMessage(new String(bytes)).build(); + stub.serverStreamingRpc(request, new StreamObserver<>() { + @Override + public void onNext(ServerStreamingResponse value) {} + + @Override + public void onError(Throwable t) { + CallServerStreamingRpc(); + } + + @Override + public void onCompleted() { + CallServerStreamingRpc(); + } + }); + } + + private void CallBidStreamingRpc() { + byte[] bytes = new byte[ThreadLocalRandom.current().nextInt(10240, 20480)]; + ThreadLocalRandom.current().nextBytes(bytes); + BidiStreamingRequest request = BidiStreamingRequest.newBuilder() + .setMessage(new String(bytes)).build(); + StreamObserver requestStreamObserver = stub.bidiStreamingRpc( + new StreamObserver<>() { + @Override + public void onNext(BidiStreamingResponse value) {} + + @Override + public void onError(Throwable t) { + CallBidStreamingRpc(); + } + + @Override + public void onCompleted() { + CallBidStreamingRpc(); + } + }); + requestStreamObserver.onNext(request); + requestStreamObserver.onCompleted(); + } + + @Override + public void run(String... args) { + CallUnaryRpc(); + CallServerStreamingRpc(); + CallClientStreamingRpc(); + CallBidStreamingRpc(); + } +} diff --git a/examples/grpc-observability/frontend/src/main/resources/application.properties b/examples/grpc-observability/frontend/src/main/resources/application.properties new file mode 100644 index 000000000..7ce12ed41 --- /dev/null +++ b/examples/grpc-observability/frontend/src/main/resources/application.properties @@ -0,0 +1,12 @@ +# Port serves the monitoring traffic. +server.port=8080 +# Expose the prometheus metrics via the monitoring port. +# By default, expose on `/actuator/prometheus`. +management.endpoints.web.exposure.include=prometheus,configprops,env,info +management.endpoint.env.show-values=ALWAYS +management.endpoint.configprops.show-values=ALWAYS +# The backend service address, for local testing. +grpc.client.backend.address=static://localhost:9091 +# Teh backend service address, for kubernetes. +# grpc.client.backend.address=dns:///backend.default.svc.cluster.local:9091 +grpc.client.backend.negotiationType=PLAINTEXT diff --git a/examples/grpc-observability/grafana/prometheus/microservices-grpc-dashboard.json b/examples/grpc-observability/grafana/prometheus/microservices-grpc-dashboard.json new file mode 100644 index 000000000..395b5a7ba --- /dev/null +++ b/examples/grpc-observability/grafana/prometheus/microservices-grpc-dashboard.json @@ -0,0 +1,1405 @@ +{ + "__inputs": [ + { + "name": "DS_PROMETHEUS", + "label": "Prometheus", + "description": "", + "type": "datasource", + "pluginId": "prometheus", + "pluginName": "Prometheus" + } + ], + "__elements": {}, + "__requires": [ + { + "type": "grafana", + "id": "grafana", + "name": "Grafana", + "version": "9.4.7" + }, + { + "type": "panel", + "id": "heatmap", + "name": "Heatmap", + "version": "" + }, + { + "type": "datasource", + "id": "prometheus", + "name": "Prometheus", + "version": "1.0.0" + }, + { + "type": "panel", + "id": "timeseries", + "name": "Time series", + "version": "" + } + ], + "annotations": { + "list": [ + { + "builtIn": 1, + "datasource": { + "type": "grafana", + "uid": "-- Grafana --" + }, + "enable": true, + "hide": true, + "iconColor": "rgba(0, 211, 255, 1)", + "name": "Annotations & Alerts", + "target": { + "limit": 100, + "matchAny": false, + "tags": [], + "type": "dashboard" + }, + "type": "dashboard" + } + ] + }, + "description": "Microservices(gRPC) dashboard with Prometheus data source. It supports the metrics defined in https://github.com/grpc/proposal/blob/master/A66-otel-stats.md", + "editable": true, + "fiscalYearStartMonth": 0, + "graphTooltip": 0, + "id": null, + "links": [], + "liveNow": false, + "panels": [ + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 0 + }, + "id": 6, + "panels": [], + "title": "Client Side Metrics", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "The total number of RPC attempts started on the client side per second, including those that have not completed.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 0, + "y": 1 + }, + "id": 2, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "sum(rate(grpc_client_attempt_started_total[$__rate_interval]))", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Client RPC Attempts Started", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "End-to-end time taken to complete an RPC attempt including the time it takes to pick a subchannel.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 9, + "w": 12, + "x": 12, + "y": 1 + }, + "id": 4, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "histogram_quantile(\n 0.5,\n sum by (le) (rate(grpc_client_attempt_duration_seconds_bucket[$__rate_interval]))\n)", + "legendFormat": "50 percentile", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "histogram_quantile(\n 0.95,\n sum by (le) (rate(grpc_client_attempt_duration_seconds_bucket[$__rate_interval]))\n)", + "hide": false, + "legendFormat": "95 percentile", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "histogram_quantile(\n 0.99,\n sum by (le) (rate(grpc_client_attempt_duration_seconds_bucket[$__rate_interval]))\n)", + "hide": false, + "legendFormat": "99 percentile", + "range": true, + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "sum(rate(grpc_client_attempt_duration_seconds_sum[$__rate_interval]))/sum(rate(grpc_client_attempt_duration_seconds_count[$__rate_interval]))", + "hide": false, + "legendFormat": "mean", + "range": true, + "refId": "D" + } + ], + "title": "Client RPC Attempt Duration (second)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Total bytes (compressed but not encrypted) sent across all request messages (metadata excluded) per second; does not include grpc or transport framing bytes.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 10 + }, + "id": 10, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.5, sum(rate(grpc_client_attempt_sent_total_compressed_message_size_bytes_bucket[$__rate_interval])) by (le))", + "legendFormat": "50 percentile", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.95, sum(rate(grpc_client_attempt_sent_total_compressed_message_size_bytes_bucket[$__rate_interval])) by (le))", + "hide": false, + "legendFormat": "95 percentile", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum(rate(grpc_client_attempt_sent_total_compressed_message_size_bytes_bucket[$__rate_interval])) by (le))", + "hide": false, + "legendFormat": "99 percentile", + "range": true, + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "sum(rate(grpc_client_attempt_sent_total_compressed_message_size_bytes_sum[$__rate_interval]))/sum(rate(grpc_client_attempt_sent_total_compressed_message_size_bytes_count[$__rate_interval]))", + "hide": false, + "legendFormat": "mean", + "range": true, + "refId": "D" + } + ], + "title": "Client Sent Compressed Message Bytes per second", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Total bytes (compressed but not encrypted) sent across all request messages (metadata excluded) per RPC attempt; does not include grpc or transport framing bytes.", + "fieldConfig": { + "defaults": { + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "scaleDistribution": { + "type": "linear" + } + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 10 + }, + "id": 8, + "options": { + "calculate": false, + "cellGap": 1, + "cellValues": { + "unit": "short" + }, + "color": { + "exponent": 0.5, + "fill": "dark-orange", + "mode": "scheme", + "reverse": false, + "scale": "exponential", + "scheme": "Oranges", + "steps": 64 + }, + "exemplars": { + "color": "rgba(255,0,255,0.7)" + }, + "filterValues": { + "le": 1e-9 + }, + "legend": { + "show": true + }, + "rowsFrame": { + "layout": "auto" + }, + "tooltip": { + "show": true, + "yHistogram": false + }, + "yAxis": { + "axisPlacement": "left", + "reverse": false, + "unit": "short" + } + }, + "pluginVersion": "9.4.7", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "sum(rate(grpc_client_attempt_sent_total_compressed_message_size_bytes_bucket[$__rate_interval])) by (le)", + "format": "heatmap", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Client Sent Compressed Message Bytes per RPC Attempt", + "type": "heatmap" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Total bytes (compressed but not encrypted) received across all response messages (metadata excluded) per second; does not include grpc or transport framing bytes.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 18 + }, + "id": 12, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.5, sum(rate(grpc_client_attempt_rcvd_total_compressed_message_size_bytes_bucket[$__rate_interval])) by (le))", + "legendFormat": "50 percentile", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.95, sum(rate(grpc_client_attempt_rcvd_total_compressed_message_size_bytes_bucket[$__rate_interval])) by (le))", + "hide": false, + "legendFormat": "95 percentile", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum(rate(grpc_client_attempt_rcvd_total_compressed_message_size_bytes_bucket[$__rate_interval])) by (le))", + "hide": false, + "legendFormat": "99 percentile", + "range": true, + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "sum(rate(grpc_client_attempt_rcvd_total_compressed_message_size_bytes_sum[$__rate_interval]))/sum(rate(grpc_client_attempt_rcvd_total_compressed_message_size_bytes_count[$__rate_interval]))", + "hide": false, + "legendFormat": "mean", + "range": true, + "refId": "D" + } + ], + "title": "Client Received Compressed Message Bytes per second", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Total bytes (compressed but not encrypted) received across all response messages (metadata excluded) per RPC attempt; does not include grpc or transport framing bytes.", + "fieldConfig": { + "defaults": { + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "scaleDistribution": { + "type": "linear" + } + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 18 + }, + "id": 14, + "options": { + "calculate": false, + "cellGap": 1, + "color": { + "exponent": 0.5, + "fill": "dark-orange", + "mode": "scheme", + "reverse": false, + "scale": "exponential", + "scheme": "Oranges", + "steps": 64 + }, + "exemplars": { + "color": "rgba(255,0,255,0.7)" + }, + "filterValues": { + "le": 1e-9 + }, + "legend": { + "show": true + }, + "rowsFrame": { + "layout": "auto" + }, + "tooltip": { + "show": true, + "yHistogram": false + }, + "yAxis": { + "axisPlacement": "left", + "reverse": false, + "unit": "short" + } + }, + "pluginVersion": "9.4.7", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "sum(rate(grpc_client_attempt_rcvd_total_compressed_message_size_bytes_bucket[$__rate_interval])) by (le)", + "format": "heatmap", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Client Received Compressed Message Bytes per RPC Attempt", + "type": "heatmap" + }, + { + "collapsed": false, + "gridPos": { + "h": 1, + "w": 24, + "x": 0, + "y": 26 + }, + "id": 16, + "panels": [], + "title": "Server Side Metrics", + "type": "row" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "The total number of RPC calls started on the server side per second, including those that have not completed.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 27 + }, + "id": 18, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "sum(rate(grpc_server_call_started_total[$__rate_interval]))", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Server Call Started", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "This metric aims to measure the end2end time an RPC takes from the server transport’s (HTTP2/ inproc) perspective.\nStart timestamp - After the transport knows that it's got a new stream. For HTTP2, this would be after the first header frame for the stream has been received and decoded. Whether the timestamp is recorded before or after HPACK is left to the implementation.\nEnd timestamp - Ends at the first point where the transport considers the stream done. For HTTP2, this would be when scheduling a trailing header with END_STREAM to be written, or RST_STREAM, or a connection abort. Note that this wouldn’t necessarily mean that the bytes have also been immediately scheduled to be written by TCP.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 27 + }, + "id": 20, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.5, sum(rate(grpc_server_call_duration_seconds_bucket[$__rate_interval])) by (le))", + "legendFormat": "50 percentile", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.95, sum(rate(grpc_server_call_duration_seconds_bucket[$__rate_interval])) by (le))", + "hide": false, + "legendFormat": "95 percentile", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum(rate(grpc_server_call_duration_seconds_bucket[$__rate_interval])) by (le))", + "hide": false, + "legendFormat": "99 percentile", + "range": true, + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "sum(rate(grpc_server_call_duration_seconds_sum[$__rate_interval]))/sum(rate(grpc_server_call_duration_seconds_count[$__rate_interval]))", + "hide": false, + "legendFormat": "mean", + "range": true, + "refId": "D" + } + ], + "title": "Server RPC Duration (second)", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Total bytes (compressed but not encrypted) sent across all response messages (metadata excluded) per second; does not include grpc or transport framing bytes.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 35 + }, + "id": 22, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.5, sum(rate(grpc_server_call_sent_total_compressed_message_size_bytes_bucket[$__rate_interval])) by (le))", + "legendFormat": "50 percentile", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.95, sum(rate(grpc_server_call_sent_total_compressed_message_size_bytes_bucket[$__rate_interval])) by (le))", + "hide": false, + "legendFormat": "95 percentile", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99, sum(rate(grpc_server_call_sent_total_compressed_message_size_bytes_bucket[$__rate_interval])) by (le))", + "hide": false, + "legendFormat": "99 percentile", + "range": true, + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "sum(rate(grpc_server_call_sent_total_compressed_message_size_bytes_sum[$__rate_interval]))/sum(rate(grpc_server_call_sent_total_compressed_message_size_bytes_count[$__rate_interval]))", + "hide": false, + "legendFormat": "mean", + "range": true, + "refId": "D" + } + ], + "title": "Server Sent Compressed Message Bytes per second", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Total bytes (compressed but not encrypted) sent across all response messages (metadata excluded) per RPC; does not include grpc or transport framing bytes.", + "fieldConfig": { + "defaults": { + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "scaleDistribution": { + "type": "linear" + } + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 35 + }, + "id": 24, + "options": { + "calculate": false, + "cellGap": 1, + "color": { + "exponent": 0.5, + "fill": "dark-orange", + "mode": "scheme", + "reverse": false, + "scale": "exponential", + "scheme": "Oranges", + "steps": 64 + }, + "exemplars": { + "color": "rgba(255,0,255,0.7)" + }, + "filterValues": { + "le": 1e-9 + }, + "legend": { + "show": true + }, + "rowsFrame": { + "layout": "auto" + }, + "tooltip": { + "show": true, + "yHistogram": false + }, + "yAxis": { + "axisPlacement": "left", + "reverse": false, + "unit": "short" + } + }, + "pluginVersion": "9.4.7", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "sum(rate(grpc_server_call_sent_total_compressed_message_size_bytes_bucket[$__rate_interval])) by (le)", + "format": "heatmap", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Server Sent Compressed Message Bytes per RPC", + "type": "heatmap" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Total bytes (compressed but not encrypted) received across all request messages (metadata excluded) per second; does not include grpc or transport framing bytes.", + "fieldConfig": { + "defaults": { + "color": { + "mode": "palette-classic" + }, + "custom": { + "axisCenteredZero": false, + "axisColorMode": "text", + "axisLabel": "", + "axisPlacement": "auto", + "barAlignment": 0, + "drawStyle": "line", + "fillOpacity": 0, + "gradientMode": "none", + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "lineInterpolation": "linear", + "lineWidth": 1, + "pointSize": 5, + "scaleDistribution": { + "type": "linear" + }, + "showPoints": "auto", + "spanNulls": false, + "stacking": { + "group": "A", + "mode": "none" + }, + "thresholdsStyle": { + "mode": "off" + } + }, + "mappings": [], + "thresholds": { + "mode": "absolute", + "steps": [ + { + "color": "green", + "value": null + }, + { + "color": "red", + "value": 80 + } + ] + }, + "unit": "short" + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 0, + "y": 43 + }, + "id": 26, + "options": { + "legend": { + "calcs": [], + "displayMode": "list", + "placement": "bottom", + "showLegend": true + }, + "tooltip": { + "mode": "single", + "sort": "none" + } + }, + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.5, sum(rate(grpc_server_call_rcvd_total_compressed_message_size_bytes_bucket[$__rate_interval])) by (le))", + "legendFormat": "50 percentile", + "range": true, + "refId": "A" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.95,sum(rate(grpc_server_call_rcvd_total_compressed_message_size_bytes_bucket[$__rate_interval])) by (le))", + "hide": false, + "legendFormat": "95 percentile", + "range": true, + "refId": "B" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "histogram_quantile(0.99,sum(rate(grpc_server_call_rcvd_total_compressed_message_size_bytes_bucket[$__rate_interval])) by (le))", + "hide": false, + "legendFormat": "99 percentile", + "range": true, + "refId": "C" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "sum(rate(grpc_server_call_rcvd_total_compressed_message_size_bytes_sum[$__rate_interval]))/sum(rate(grpc_server_call_rcvd_total_compressed_message_size_bytes_count[$__rate_interval]))", + "hide": false, + "legendFormat": "mean", + "range": true, + "refId": "D" + } + ], + "title": "Server Received Compressed Message Bytes per second", + "type": "timeseries" + }, + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "description": "Total bytes (compressed but not encrypted) received across all request messages (metadata excluded) per RPC; does not include grpc or transport framing bytes.", + "fieldConfig": { + "defaults": { + "custom": { + "hideFrom": { + "legend": false, + "tooltip": false, + "viz": false + }, + "scaleDistribution": { + "type": "linear" + } + } + }, + "overrides": [] + }, + "gridPos": { + "h": 8, + "w": 12, + "x": 12, + "y": 43 + }, + "id": 28, + "options": { + "calculate": false, + "cellGap": 1, + "color": { + "exponent": 0.5, + "fill": "dark-orange", + "mode": "scheme", + "reverse": false, + "scale": "exponential", + "scheme": "Oranges", + "steps": 64 + }, + "exemplars": { + "color": "rgba(255,0,255,0.7)" + }, + "filterValues": { + "le": 1e-9 + }, + "legend": { + "show": true + }, + "rowsFrame": { + "layout": "auto" + }, + "tooltip": { + "show": true, + "yHistogram": false + }, + "yAxis": { + "axisPlacement": "left", + "reverse": false, + "unit": "short" + } + }, + "pluginVersion": "9.4.7", + "targets": [ + { + "datasource": { + "type": "prometheus", + "uid": "${DS_PROMETHEUS}" + }, + "editorMode": "code", + "expr": "sum(rate(grpc_server_call_rcvd_total_compressed_message_size_bytes_bucket[$__rate_interval])) by (le)", + "format": "heatmap", + "legendFormat": "__auto", + "range": true, + "refId": "A" + } + ], + "title": "Server Received Compressed Message Bytes per RPC", + "type": "heatmap" + } + ], + "refresh": "", + "revision": 1, + "schemaVersion": 38, + "style": "dark", + "tags": [], + "templating": { + "list": [] + }, + "time": { + "from": "now-3d", + "to": "now" + }, + "timepicker": {}, + "timezone": "", + "title": "Microservices (gRPC) Monitoring", + "uid": "-ABC1NOTy", + "version": 17, + "weekStart": "" +} diff --git a/examples/grpc-observability/pod_monitoring.yaml b/examples/grpc-observability/pod_monitoring.yaml new file mode 100644 index 000000000..2ccfb7052 --- /dev/null +++ b/examples/grpc-observability/pod_monitoring.yaml @@ -0,0 +1,12 @@ +apiVersion: monitoring.googleapis.com/v1 +kind: PodMonitoring +metadata: + name: prometheus +spec: + selector: + matchLabels: + monitor: prometheus + endpoints: + - port: monitoring + path: /actuator/prometheus + interval: 30s diff --git a/examples/grpc-observability/proto/build.gradle b/examples/grpc-observability/proto/build.gradle new file mode 100644 index 000000000..dcabecc22 --- /dev/null +++ b/examples/grpc-observability/proto/build.gradle @@ -0,0 +1,44 @@ +// Copyright 2023 The gRPC-GCP-Mobile Authors +// All rights reserved. +// +// 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. +// +plugins { + id 'com.google.protobuf' +} + +protobuf { + protoc { + artifact = "com.google.protobuf:protoc:${protobufVersion}" + } + plugins { + grpc { + artifact = "io.grpc:protoc-gen-grpc-java:${grpcVersion}" + } + } + generateProtoTasks { + all()*.plugins { + grpc {} + } + } +} + +dependencies { + implementation 'io.grpc:grpc-protobuf' + implementation 'io.grpc:grpc-stub' + if (JavaVersion.current().isJava9Compatible()) { + // Workaround for @javax.annotation.Generated + // see: https://github.com/grpc/grpc-java/issues/3633 + implementation 'javax.annotation:javax.annotation-api:1.3.1' + } +} diff --git a/examples/grpc-observability/proto/src/main/proto/backend.proto b/examples/grpc-observability/proto/src/main/proto/backend.proto new file mode 100644 index 000000000..691a67375 --- /dev/null +++ b/examples/grpc-observability/proto/src/main/proto/backend.proto @@ -0,0 +1,61 @@ +// Copyright 2023 The gRPC-GCP-Mobile Authors +// All rights reserved. +// +// 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. + +syntax = "proto3"; + +package net.devh.boot.grpc.examples.observability.proto; + +option java_package = "net.devh.boot.grpc.examples.observability.proto"; +option java_outer_classname = "ExampleServiceProto"; +option java_multiple_files = true; + +service ExampleService { + rpc UnaryRpc (UnaryRequest) returns (UnaryResponse) {} + rpc ClientStreamingRpc (stream ClientStreamingRequest) returns (ClientStreamingResponse) {} + rpc ServerStreamingRpc (ServerStreamingRequest) returns (stream ServerStreamingResponse) {} + rpc BidiStreamingRpc (stream BidiStreamingRequest) returns (stream BidiStreamingResponse) {} +} + +message UnaryRequest { + string message = 1; +} + +message UnaryResponse { + string message = 1; +} + +message ClientStreamingRequest { + string message = 1; +} + +message ClientStreamingResponse { + string message = 1; +} + +message ServerStreamingRequest { + string message = 1; +} + +message ServerStreamingResponse { + string message = 1; +} + +message BidiStreamingRequest { + string message = 1; +} + +message BidiStreamingResponse { + string message = 1; +} diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7f93135c4..e6441136f 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 3fa8f862f..b82aa23a4 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew.bat b/gradlew.bat index 93e3f59f1..25da30dbd 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -43,11 +43,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail diff --git a/grpc-client-spring-boot-autoconfigure/build.gradle b/grpc-client-spring-boot-autoconfigure/build.gradle deleted file mode 100644 index 17662d2c6..000000000 --- a/grpc-client-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' - api 'jakarta.validation:jakarta.validation-api' - optionalSupportImplementation "io.micrometer:micrometer-observation" - optionalSupportImplementation 'org.springframework.boot:spring-boot-starter-actuator' - optionalSupportImplementation 'org.springframework.cloud:spring-cloud-starter-consul-discovery' - optionalSupportImplementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client' - optionalSupportImplementation 'io.zipkin.brave:brave-instrumentation-grpc' - optionalSupportImplementation 'javax.inject:javax.inject:1' - // api comes from java-library, and allows exposure of the dependency to consumers of the library - // this means it can be used implicitly without specifying the dependency (unless you wish to override with a different version, or exclude) - optionalSupportApi 'io.grpc:grpc-netty' - optionalSupportApi 'io.netty:netty-transport-native-epoll' - api 'io.grpc:grpc-inprocess' - api 'io.grpc:grpc-netty-shaded' - api 'io.grpc:grpc-protobuf' - api 'io.grpc:grpc-stub' - - testImplementation 'io.grpc:grpc-testing' - testImplementation('org.springframework.boot:spring-boot-starter-test') -} diff --git a/grpc-client-spring-boot-starter/build.gradle b/grpc-client-spring-boot-starter/build.gradle index 2098c4b39..17662d2c6 100644 --- a/grpc-client-spring-boot-starter/build.gradle +++ b/grpc-client-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-client-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' + api 'jakarta.validation:jakarta.validation-api' + optionalSupportImplementation "io.micrometer:micrometer-observation" + optionalSupportImplementation 'org.springframework.boot:spring-boot-starter-actuator' + optionalSupportImplementation 'org.springframework.cloud:spring-cloud-starter-consul-discovery' + optionalSupportImplementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client' + optionalSupportImplementation 'io.zipkin.brave:brave-instrumentation-grpc' + optionalSupportImplementation 'javax.inject:javax.inject:1' + // api comes from java-library, and allows exposure of the dependency to consumers of the library + // this means it can be used implicitly without specifying the dependency (unless you wish to override with a different version, or exclude) + optionalSupportApi 'io.grpc:grpc-netty' + optionalSupportApi 'io.netty:netty-transport-native-epoll' + api 'io.grpc:grpc-inprocess' + api 'io.grpc:grpc-netty-shaded' + api 'io.grpc:grpc-protobuf' + api 'io.grpc:grpc-stub' + + testImplementation 'io.grpc:grpc-testing' + testImplementation('org.springframework.boot:spring-boot-starter-test') } diff --git a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/autoconfigure/GrpcClientAutoConfiguration.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/autoconfigure/GrpcClientAutoConfiguration.java similarity index 100% rename from grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/autoconfigure/GrpcClientAutoConfiguration.java rename to grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/autoconfigure/GrpcClientAutoConfiguration.java diff --git a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/autoconfigure/GrpcClientHealthAutoConfiguration.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/autoconfigure/GrpcClientHealthAutoConfiguration.java similarity index 100% rename from grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/autoconfigure/GrpcClientHealthAutoConfiguration.java rename to grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/autoconfigure/GrpcClientHealthAutoConfiguration.java diff --git a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/autoconfigure/GrpcClientMetricAutoConfiguration.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/autoconfigure/GrpcClientMetricAutoConfiguration.java similarity index 75% rename from grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/autoconfigure/GrpcClientMetricAutoConfiguration.java rename to grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/autoconfigure/GrpcClientMetricAutoConfiguration.java index 883eb92f9..d18c98a9d 100644 --- a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/autoconfigure/GrpcClientMetricAutoConfiguration.java +++ b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/autoconfigure/GrpcClientMetricAutoConfiguration.java @@ -22,13 +22,17 @@ 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.Configuration; import org.springframework.core.annotation.Order; +import com.google.common.base.Stopwatch; + import io.grpc.ClientInterceptor; import io.micrometer.core.instrument.MeterRegistry; import io.micrometer.core.instrument.binder.grpc.MetricCollectingClientInterceptor; import net.devh.boot.grpc.client.interceptor.GrpcGlobalClientInterceptor; +import net.devh.boot.grpc.client.metrics.MetricsClientInterceptor; import net.devh.boot.grpc.common.util.InterceptorOrder; /** @@ -56,4 +60,18 @@ public MetricCollectingClientInterceptor metricCollectingClientInterceptor(final return new MetricCollectingClientInterceptor(registry); } + /** + * Creates a {@link ClientInterceptor} that collects metrics about client attempts and client calls. + * + * @param registry The registry used to create the metrics. + * @return The newly created MetricsClientInterceptor bean. + */ + @ConditionalOnProperty(prefix = "grpc", name = "metricsA66Enabled", matchIfMissing = true) + @GrpcGlobalClientInterceptor + @Order(InterceptorOrder.ORDER_TRACING_METRICS) + @ConditionalOnMissingBean + public MetricsClientInterceptor metricsClientInterceptor(final MeterRegistry registry) { + return new MetricsClientInterceptor(registry, Stopwatch::createUnstarted); + } + } diff --git a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/autoconfigure/GrpcClientMicrometerTraceAutoConfiguration.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/autoconfigure/GrpcClientMicrometerTraceAutoConfiguration.java similarity index 100% rename from grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/autoconfigure/GrpcClientMicrometerTraceAutoConfiguration.java rename to grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/autoconfigure/GrpcClientMicrometerTraceAutoConfiguration.java diff --git a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/autoconfigure/GrpcClientSecurityAutoConfiguration.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/autoconfigure/GrpcClientSecurityAutoConfiguration.java similarity index 100% rename from grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/autoconfigure/GrpcClientSecurityAutoConfiguration.java rename to grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/autoconfigure/GrpcClientSecurityAutoConfiguration.java diff --git a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/autoconfigure/GrpcDiscoveryClientAutoConfiguration.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/autoconfigure/GrpcDiscoveryClientAutoConfiguration.java similarity index 100% rename from grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/autoconfigure/GrpcDiscoveryClientAutoConfiguration.java rename to grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/autoconfigure/GrpcDiscoveryClientAutoConfiguration.java diff --git a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/autoconfigure/package-info.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/autoconfigure/package-info.java similarity index 100% rename from grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/autoconfigure/package-info.java rename to grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/autoconfigure/package-info.java diff --git a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/channelfactory/AbstractChannelFactory.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/channelfactory/AbstractChannelFactory.java similarity index 96% rename from grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/channelfactory/AbstractChannelFactory.java rename to grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/channelfactory/AbstractChannelFactory.java index d403bc845..e16f79770 100644 --- a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/channelfactory/AbstractChannelFactory.java +++ b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/channelfactory/AbstractChannelFactory.java @@ -181,7 +181,7 @@ protected void configure(final T builder, final String name) { configureKeepAlive(builder, name); configureSecurity(builder, name); configureLimits(builder, name); - configureCompression(builder, name); + configureUserAgent(builder, name); for (final GrpcChannelConfigurer channelConfigurer : this.channelConfigurers) { channelConfigurer.accept(builder, name); } @@ -233,7 +233,7 @@ protected boolean isNonNullAndNonBlank(final String value) { } /** - * Configures limits such as max message sizes that should be used by the channel. + * Configures limits such as max message or metadata sizes that should be used by the channel. * * @param builder The channel builder to configure. * @param name The name of the client to configure. @@ -244,18 +244,23 @@ protected void configureLimits(final T builder, final String name) { if (maxInboundMessageSize != null) { builder.maxInboundMessageSize((int) maxInboundMessageSize.toBytes()); } + final DataSize maxInboundMetadataSize = properties.getMaxInboundMetadataSize(); + if (maxInboundMetadataSize != null) { + builder.maxInboundMetadataSize((int) maxInboundMetadataSize.toBytes()); + } } /** - * Configures the compression options that should be used by the channel. + * Configures custom User-Agent for the channel. * * @param builder The channel builder to configure. * @param name The name of the client to configure. */ - protected void configureCompression(final T builder, final String name) { + protected void configureUserAgent(final T builder, final String name) { final GrpcChannelProperties properties = getPropertiesFor(name); - if (properties.isFullStreamDecompression()) { - builder.enableFullStreamDecompression(); + final String userAgent = properties.getUserAgent(); + if (userAgent != null) { + builder.userAgent(userAgent); } } diff --git a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/channelfactory/GrpcChannelConfigurer.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/channelfactory/GrpcChannelConfigurer.java similarity index 100% rename from grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/channelfactory/GrpcChannelConfigurer.java rename to grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/channelfactory/GrpcChannelConfigurer.java diff --git a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/channelfactory/GrpcChannelFactory.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/channelfactory/GrpcChannelFactory.java similarity index 100% rename from grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/channelfactory/GrpcChannelFactory.java rename to grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/channelfactory/GrpcChannelFactory.java diff --git a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/channelfactory/InProcessChannelFactory.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/channelfactory/InProcessChannelFactory.java similarity index 100% rename from grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/channelfactory/InProcessChannelFactory.java rename to grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/channelfactory/InProcessChannelFactory.java diff --git a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/channelfactory/InProcessOrAlternativeChannelFactory.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/channelfactory/InProcessOrAlternativeChannelFactory.java similarity index 100% rename from grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/channelfactory/InProcessOrAlternativeChannelFactory.java rename to grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/channelfactory/InProcessOrAlternativeChannelFactory.java diff --git a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/channelfactory/NettyChannelFactory.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/channelfactory/NettyChannelFactory.java similarity index 100% rename from grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/channelfactory/NettyChannelFactory.java rename to grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/channelfactory/NettyChannelFactory.java diff --git a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/channelfactory/ShadedNettyChannelFactory.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/channelfactory/ShadedNettyChannelFactory.java similarity index 100% rename from grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/channelfactory/ShadedNettyChannelFactory.java rename to grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/channelfactory/ShadedNettyChannelFactory.java diff --git a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/channelfactory/package-info.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/channelfactory/package-info.java similarity index 100% rename from grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/channelfactory/package-info.java rename to grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/channelfactory/package-info.java diff --git a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/config/GrpcChannelProperties.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/config/GrpcChannelProperties.java similarity index 92% rename from grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/config/GrpcChannelProperties.java rename to grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/config/GrpcChannelProperties.java index c99fc8ddd..2d06a4e2c 100644 --- a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/config/GrpcChannelProperties.java +++ b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/config/GrpcChannelProperties.java @@ -333,35 +333,43 @@ public void setMaxInboundMessageSize(final DataSize maxInboundMessageSize) { } } - // -------------------------------------------------- - - private Boolean fullStreamDecompression; - private static final boolean DEFAULT_FULL_STREAM_DECOMPRESSION = false; + @DataSizeUnit(DataUnit.BYTES) + private DataSize maxInboundMetadataSize = null; /** - * Gets whether full-stream decompression of inbound streams should be enabled. + * Sets the maximum size of metadata in bytes allowed to be received. If not set ({@code null}) then it will default + * to gRPC's default. The default is implementation-dependent, but is not generally less than 8 KiB and may be + * unlimited. If set to {@code -1} then it will use the highest possible limit (not recommended). Integer.MAX_VALUE + * disables the enforcement. * - * @return True, if full-stream decompression of inbound streams should be enabled. False otherwise. + * @return The maximum size of metadata in bytes allowed to be received or null if the default should be used. * - * @see #setFullStreamDecompression(Boolean) + * @see ManagedChannelBuilder#maxInboundMetadataSize(int) (int) */ - public boolean isFullStreamDecompression() { - return this.fullStreamDecompression == null ? DEFAULT_FULL_STREAM_DECOMPRESSION : this.fullStreamDecompression; + public DataSize getMaxInboundMetadataSize() { + return maxInboundMetadataSize; } /** - * Sets whether full-stream decompression of inbound streams should be enabled. This will cause the channel's - * outbound headers to advertise support for GZIP compressed streams, and gRPC servers which support the feature may - * respond with a GZIP compressed stream. + * Sets the maximum size of metadata in bytes allowed to be received. If not set ({@code null}) then it will + * default.The default is implementation-dependent, but is not generally less than 8 KiB and may be unlimited. If + * set to {@code -1} then it will use the highest possible limit (not recommended). Integer.MAX_VALUE disables the + * enforcement. * - * @param fullStreamDecompression Whether full stream decompression should be enabled or null to use the fallback. + * @param maxInboundMetadataSize The new maximum size of metadata in bytes allowed to be received. {@code -1} for + * max possible. Null to use the gRPC's default. * - * @see ManagedChannelBuilder#enableFullStreamDecompression() + * @see ManagedChannelBuilder#maxInboundMetadataSize(int) (int) */ - public void setFullStreamDecompression(final Boolean fullStreamDecompression) { - this.fullStreamDecompression = fullStreamDecompression; + public void setMaxInboundMetadataSize(DataSize maxInboundMetadataSize) { + if (maxInboundMetadataSize == null || maxInboundMetadataSize.toBytes() >= 0) { + this.maxInboundMetadataSize = maxInboundMetadataSize; + } else if (maxInboundMetadataSize.toBytes() == -1) { + this.maxInboundMetadataSize = DataSize.ofBytes(Integer.MAX_VALUE); + } else { + throw new IllegalArgumentException("Unsupported maxInboundMetadataSize: " + maxInboundMetadataSize); + } } - // -------------------------------------------------- private NegotiationType negotiationType; @@ -422,6 +430,32 @@ public void setImmediateConnectTimeout(final Duration immediateConnectTimeout) { // -------------------------------------------------- + private String userAgent = null; + + /** + * Get custom User-Agent for the channel. + * + * @return custom User-Agent for the channel. + * + * @see #setUserAgent(String) + */ + public String getUserAgent() { + return this.userAgent; + } + + /** + * Sets custom User-Agent HTTP header. + * + * @param userAgent Custom User-Agent. + * + * @see ManagedChannelBuilder#userAgent(String) + */ + public void setUserAgent(final String userAgent) { + this.userAgent = userAgent; + } + + // -------------------------------------------------- + private final Security security = new Security(); /** @@ -467,8 +501,8 @@ public void copyDefaultsFrom(final GrpcChannelProperties config) { if (this.maxInboundMessageSize == null) { this.maxInboundMessageSize = config.maxInboundMessageSize; } - if (this.fullStreamDecompression == null) { - this.fullStreamDecompression = config.fullStreamDecompression; + if (this.maxInboundMetadataSize == null) { + this.maxInboundMetadataSize = config.maxInboundMetadataSize; } if (this.negotiationType == null) { this.negotiationType = config.negotiationType; @@ -476,6 +510,9 @@ public void copyDefaultsFrom(final GrpcChannelProperties config) { if (this.immediateConnectTimeout == null) { this.immediateConnectTimeout = config.immediateConnectTimeout; } + if (this.userAgent == null) { + this.userAgent = config.userAgent; + } this.security.copyDefaultsFrom(config.security); } diff --git a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/config/GrpcChannelsProperties.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/config/GrpcChannelsProperties.java similarity index 100% rename from grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/config/GrpcChannelsProperties.java rename to grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/config/GrpcChannelsProperties.java diff --git a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/config/NegotiationType.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/config/NegotiationType.java similarity index 100% rename from grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/config/NegotiationType.java rename to grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/config/NegotiationType.java diff --git a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/config/package-info.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/config/package-info.java similarity index 100% rename from grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/config/package-info.java rename to grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/config/package-info.java diff --git a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/inject/GrpcClient.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/inject/GrpcClient.java similarity index 100% rename from grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/inject/GrpcClient.java rename to grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/inject/GrpcClient.java diff --git a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/inject/GrpcClientBean.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/inject/GrpcClientBean.java similarity index 100% rename from grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/inject/GrpcClientBean.java rename to grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/inject/GrpcClientBean.java diff --git a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/inject/GrpcClientBeanPostProcessor.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/inject/GrpcClientBeanPostProcessor.java similarity index 100% rename from grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/inject/GrpcClientBeanPostProcessor.java rename to grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/inject/GrpcClientBeanPostProcessor.java diff --git a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/inject/GrpcClientBeans.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/inject/GrpcClientBeans.java similarity index 100% rename from grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/inject/GrpcClientBeans.java rename to grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/inject/GrpcClientBeans.java diff --git a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/inject/GrpcClientConstructorInjection.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/inject/GrpcClientConstructorInjection.java similarity index 100% rename from grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/inject/GrpcClientConstructorInjection.java rename to grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/inject/GrpcClientConstructorInjection.java diff --git a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/inject/GrpcClientConstructorInjectionBeanFactoryPostProcessor.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/inject/GrpcClientConstructorInjectionBeanFactoryPostProcessor.java similarity index 100% rename from grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/inject/GrpcClientConstructorInjectionBeanFactoryPostProcessor.java rename to grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/inject/GrpcClientConstructorInjectionBeanFactoryPostProcessor.java diff --git a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/inject/StubTransformer.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/inject/StubTransformer.java similarity index 100% rename from grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/inject/StubTransformer.java rename to grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/inject/StubTransformer.java diff --git a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/inject/package-info.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/inject/package-info.java similarity index 100% rename from grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/inject/package-info.java rename to grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/inject/package-info.java diff --git a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/interceptor/AnnotationGlobalClientInterceptorConfigurer.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/interceptor/AnnotationGlobalClientInterceptorConfigurer.java similarity index 100% rename from grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/interceptor/AnnotationGlobalClientInterceptorConfigurer.java rename to grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/interceptor/AnnotationGlobalClientInterceptorConfigurer.java diff --git a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/interceptor/GlobalClientInterceptorConfigurer.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/interceptor/GlobalClientInterceptorConfigurer.java similarity index 100% rename from grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/interceptor/GlobalClientInterceptorConfigurer.java rename to grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/interceptor/GlobalClientInterceptorConfigurer.java diff --git a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/interceptor/GlobalClientInterceptorRegistry.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/interceptor/GlobalClientInterceptorRegistry.java similarity index 100% rename from grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/interceptor/GlobalClientInterceptorRegistry.java rename to grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/interceptor/GlobalClientInterceptorRegistry.java diff --git a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/interceptor/GrpcGlobalClientInterceptor.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/interceptor/GrpcGlobalClientInterceptor.java similarity index 100% rename from grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/interceptor/GrpcGlobalClientInterceptor.java rename to grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/interceptor/GrpcGlobalClientInterceptor.java diff --git a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/interceptor/OrderedClientInterceptor.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/interceptor/OrderedClientInterceptor.java similarity index 100% rename from grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/interceptor/OrderedClientInterceptor.java rename to grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/interceptor/OrderedClientInterceptor.java diff --git a/grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/interceptor/package-info.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/interceptor/package-info.java similarity index 100% rename from grpc-client-spring-boot-autoconfigure/src/main/java/net/devh/boot/grpc/client/interceptor/package-info.java rename to grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/interceptor/package-info.java diff --git a/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/metrics/MetricsClientInstruments.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/metrics/MetricsClientInstruments.java new file mode 100644 index 000000000..6b419d0b6 --- /dev/null +++ b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/metrics/MetricsClientInstruments.java @@ -0,0 +1,101 @@ +/* + * 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.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 client. + */ +public final class MetricsClientInstruments { + + private MetricsClientInstruments() {} + + /* + * Client 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 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 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 MetricsClientMeters newClientMetricsMeters(MeterRegistry registry) { + MetricsClientMeters.Builder builder = MetricsClientMeters.newBuilder(); + + builder.setAttemptCounter(Counter.builder(CLIENT_ATTEMPT_STARTED) + .description( + "The total number of RPC attempts started from the client side, including " + + "those that have not completed.") + .withRegistry(registry)); + + builder.setSentMessageSizeDistribution(DistributionSummary.builder( + CLIENT_ATTEMPT_SENT_COMPRESSED_MESSAGE_SIZE) + .description("Compressed message bytes sent per client call attempt") + .baseUnit(BaseUnits.BYTES) + .serviceLevelObjectives(DEFAULT_SIZE_BUCKETS) + .withRegistry(registry)); + + builder.setReceivedMessageSizeDistribution(DistributionSummary.builder( + CLIENT_ATTEMPT_RECEIVED_COMPRESSED_MESSAGE_SIZE) + .description("Compressed message bytes received per call attempt") + .baseUnit(BaseUnits.BYTES) + .serviceLevelObjectives(DEFAULT_SIZE_BUCKETS) + .withRegistry(registry)); + + builder.setClientAttemptDuration(Timer.builder(CLIENT_ATTEMPT_DURATION) + .description("Time taken to complete a client call attempt") + .serviceLevelObjectives(DEFAULT_LATENCY_BUCKETS) + .withRegistry(registry)); + + builder.setClientCallDuration(Timer.builder(CLIENT_CALL_DURATION) + .description("Time taken by gRPC to complete an RPC from application's perspective") + .serviceLevelObjectives(DEFAULT_LATENCY_BUCKETS) + .withRegistry(registry)); + + return builder.build(); + } + +} diff --git a/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/metrics/MetricsClientInterceptor.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/metrics/MetricsClientInterceptor.java new file mode 100644 index 000000000..6f3caaa33 --- /dev/null +++ b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/metrics/MetricsClientInterceptor.java @@ -0,0 +1,91 @@ +/* + * 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.function.Supplier; + +import com.google.common.base.Stopwatch; + +import io.grpc.CallOptions; +import io.grpc.Channel; +import io.grpc.ClientCall; +import io.grpc.ClientInterceptor; +import io.grpc.ForwardingClientCall.SimpleForwardingClientCall; +import io.grpc.ForwardingClientCallListener.SimpleForwardingClientCallListener; +import io.grpc.Metadata; +import io.grpc.MethodDescriptor; +import io.grpc.Status; +import io.micrometer.core.instrument.MeterRegistry; + +/** + * A gRPC client interceptor that collects gRPC metrics. + * + * Note: This class uses experimental grpc-java-API features. + */ +public class MetricsClientInterceptor implements ClientInterceptor { + + private final MetricsClientMeters metricsClientMeters; + private final Supplier stopwatchSupplier; + + /** + * Creates a new gRPC client interceptor that collects metrics into the given + * {@link io.micrometer.core.instrument.MeterRegistry}. + * + * @param registry The MeterRegistry to use. + */ + public MetricsClientInterceptor(MeterRegistry registry, Supplier stopwatchSupplier) { + this(MetricsClientInstruments.newClientMetricsMeters(registry), stopwatchSupplier); + } + + public MetricsClientInterceptor(MetricsClientMeters meters, Supplier stopwatchSupplier) { + this.metricsClientMeters = meters; + this.stopwatchSupplier = stopwatchSupplier; + } + + @Override + public ClientCall interceptCall( + MethodDescriptor method, CallOptions callOptions, Channel next) { + + /* + * This is a per call ClientStreamTracer.Factory which creates a new stream tracer for each attempt under the + * same call. Each call needs a dedicated factory as they share the same method descriptor. + */ + final MetricsClientStreamTracers.CallAttemptsTracerFactory tracerFactory = + new MetricsClientStreamTracers.CallAttemptsTracerFactory( + new MetricsClientStreamTracers(stopwatchSupplier), + method.getFullMethodName(), + metricsClientMeters); + + ClientCall call = + next.newCall(method, callOptions.withStreamTracerFactory(tracerFactory)); + + return new SimpleForwardingClientCall(call) { + @Override + public void start(Listener responseListener, Metadata headers) { + delegate().start( + new SimpleForwardingClientCallListener(responseListener) { + @Override + public void onClose(Status status, Metadata trailers) { + tracerFactory.callEnded(status); + super.onClose(status, trailers); + } + }, + headers); + } + }; + } +} diff --git a/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/metrics/MetricsClientMeters.java b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/metrics/MetricsClientMeters.java new file mode 100644 index 000000000..6330cc6f8 --- /dev/null +++ b/grpc-client-spring-boot-starter/src/main/java/net/devh/boot/grpc/client/metrics/MetricsClientMeters.java @@ -0,0 +1,106 @@ +/* + * 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 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 client metrics meters. + */ +public class MetricsClientMeters { + + private MeterProvider attemptCounter; + private MeterProvider sentMessageSizeDistribution; + private MeterProvider receivedMessageSizeDistribution; + private MeterProvider clientAttemptDuration; + private MeterProvider clientCallDuration; + + private MetricsClientMeters(Builder builder) { + this.attemptCounter = builder.attemptCounter; + this.sentMessageSizeDistribution = builder.sentMessageSizeDistribution; + this.receivedMessageSizeDistribution = builder.receivedMessageSizeDistribution; + this.clientAttemptDuration = builder.clientAttemptDuration; + this.clientCallDuration = builder.clientCallDuration; + } + + public MeterProvider getAttemptCounter() { + return this.attemptCounter; + } + + public MeterProvider getSentMessageSizeDistribution() { + return this.sentMessageSizeDistribution; + } + + public MeterProvider getReceivedMessageSizeDistribution() { + return this.receivedMessageSizeDistribution; + } + + public MeterProvider getClientAttemptDuration() { + return this.clientAttemptDuration; + } + + public MeterProvider getClientCallDuration() { + return this.clientCallDuration; + } + + public static Builder newBuilder() { + return new Builder(); + } + + static class Builder { + + private MeterProvider attemptCounter; + private MeterProvider sentMessageSizeDistribution; + private MeterProvider receivedMessageSizeDistribution; + private MeterProvider clientAttemptDuration; + private MeterProvider clientCallDuration; + + private Builder() {} + + public Builder setAttemptCounter(MeterProvider counter) { + this.attemptCounter = 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 setClientAttemptDuration(MeterProvider timer) { + this.clientAttemptDuration = timer; + return this; + } + + public Builder setClientCallDuration(MeterProvider 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); + } +}