This is an automated email from the ASF dual-hosted git repository.
Alanxtl pushed a commit to branch develop
in repository https://gitbox.apache.org/repos/asf/dubbo-go.git
The following commit(s) were added to refs/heads/develop by this push:
new 51154987a feat(tools): add benchmark suite for Dubbo-Go, Dubbo-Java
and gRPC (#3502)
51154987a is described below
commit 51154987a2d0c77b9b4ccf879d266512ba445ba2
Author: _衣服修改 <[email protected]>
AuthorDate: Wed Aug 5 07:47:07 2026 +0800
feat(tools): add benchmark suite for Dubbo-Go, Dubbo-Java and gRPC (#3502)
* feat: add benchmark suite for Dubbo-Go, Dubbo-Java and gRPC
- Add benchmark framework with client/server implementations
- Support Dubbo-Go, Dubbo-Java and gRPC protocols
- Add config.yaml for test parameters
- Use standard protoc-gen-go-triple plugin for triple code generation
- Add .gitignore rules for benchmark artifacts
* fix: add replace directive in benchmark go.mod to use local dubbo-go
* fix: remove benchmark go.mod to avoid replace directive in CI
Remove tools/benchmark/go.mod and go.sum to integrate benchmark
as part of the main project. This avoids CI failures caused by
replace directive pointing to local paths which don't work in CI.
* fix: address lint issues in benchmark code
- Fix error strings capitalization (ST1005)
- Fix shadow variable declarations
- Add generated file lint exclusion comments
- Ensure code formatting passes make fmt
* Update integrate_test.sh
* update readme:tools/benchmark的介绍
* fix: integrate_test.sh use local path replace and add benchmark docs to
README
- Fix integrate_test.sh to use local checked-out dubbo-go code
instead of remote path which causes 500 error for fork PRs
- Add benchmark tool introduction to README.md and README_CN.md
* fix: format statistics.go with slices.Sort and fix integrate_test.sh
- Replace sort.Slice with slices.Sort for Go 1.21+ idiomatic code
- Fix integrate_test.sh to use local path replace instead of remote
fork module resolution which causes 500 Internal Server Error in CI
* fix: fallback to working directory when data dir not writable
* feat: run full benchmark suite and update README with real test results
- Synced with upstream develop v3.3.2
- Ran 16 benchmark test cases (2 frameworks x 4 payloads x 2 concurrency)
- Fixed data directory fallback when executable path not writable
- Updated README.md and README_CN.md with actual benchmark data:
- Dubbo-Go: 19.7k-310 QPS, 3.3-795ms P99 latency
- gRPC: 106.6k-1,453 QPS, 0.94-109ms P99 latency
- Resource usage comparison
* fix:readme
* update readme
* sync readme_CN
* Update README_CN.md
* Update README_CN.md
* Update dubbo_client.go
* fix
* fix
* Update main.go
* update readme
---
README.md | 1 +
README_CN.md | 1 +
tools/benchmark/README.md | 299 ++++++++++++++++++
tools/benchmark/README_CN.md | 299 ++++++++++++++++++
tools/benchmark/client/clients/dubbo_client.go | 112 +++++++
tools/benchmark/client/clients/grpc_client.go | 96 ++++++
tools/benchmark/client/engine/engine.go | 125 ++++++++
tools/benchmark/client/engine/metrics.go | 87 ++++++
tools/benchmark/client/engine/statistics.go | 110 +++++++
tools/benchmark/client/main.go | 344 +++++++++++++++++++++
tools/benchmark/client/monitor/system_monitor.go | 195 ++++++++++++
tools/benchmark/client/payload/payload.go | 63 ++++
tools/benchmark/config.yaml | 55 ++++
tools/benchmark/proto/benchmark.pb.go | 194 ++++++++++++
tools/benchmark/proto/benchmark.proto | 37 +++
tools/benchmark/proto/benchmark.triple.go | 209 +++++++++++++
tools/benchmark/proto/benchmark_grpc.pb.go | 170 ++++++++++
tools/benchmark/scripts/gen_code.sh | 57 ++++
tools/benchmark/scripts/run_all.sh | 178 +++++++++++
tools/benchmark/scripts/run_single.sh | 139 +++++++++
tools/benchmark/server/dubbo-go/main.go | 122 ++++++++
tools/benchmark/server/dubbo-java/pom.xml | 109 +++++++
.../apache/dubbo/benchmark/BenchmarkRequest.java | 37 +++
.../apache/dubbo/benchmark/BenchmarkResponse.java | 37 +++
.../apache/dubbo/benchmark/BenchmarkServer.java | 41 +++
.../apache/dubbo/benchmark/BenchmarkService.java | 22 ++
.../dubbo/benchmark/BenchmarkServiceImpl.java | 29 ++
.../src/main/resources/application.properties | 7 +
tools/benchmark/server/grpc/main.go | 101 ++++++
29 files changed, 3276 insertions(+)
diff --git a/README.md b/README.md
index 34e00ac1e..8463a2cae 100644
--- a/README.md
+++ b/README.md
@@ -117,6 +117,7 @@ Common development tools live in `tools/`.
| [protoc-gen-triple-openapi](./tools/protoc-gen-triple-openapi/README.md) |
Generate OpenAPI v3 documents from Triple protobuf services. |
| [imports-formatter](./tools/imports-formatter/README.md) | Format Go import
blocks using dubbo-go grouping rules. |
| [dubbo-go-schema](./tools/dubbo-go-schema/README.md) | Provide JSON Schema
completion and validation for dubbo-go YAML config. |
+| [benchmark](./tools/benchmark/README.md) | Performance benchmark suite for
comparing Dubbo-Go, Dubbo-Java, and gRPC frameworks. |
## Ecosystem
- [dubbo-go-samples](https://github.com/apache/dubbo-go-samples)
diff --git a/README_CN.md b/README_CN.md
index 6dd0dde01..21663effd 100644
--- a/README_CN.md
+++ b/README_CN.md
@@ -109,6 +109,7 @@ func main() {
| [protoc-gen-triple-openapi](./tools/protoc-gen-triple-openapi/README_CN.md)
| 从 Triple protobuf 服务生成 OpenAPI v3 文档。 |
| [imports-formatter](./tools/imports-formatter/README_CN.md) | 按 dubbo-go
分组规则整理 Go import 块。 |
| [dubbo-go-schema](./tools/dubbo-go-schema/README_CN.md) | 为 dubbo-go YAML
配置提供 JSON Schema 补全与校验。 |
+| [benchmark](./tools/benchmark/README_CN.md) | 对比 Dubbo-Go、Dubbo-Java 和 gRPC
框架性能的基准测试套件。 |
## 生态系统
diff --git a/tools/benchmark/README.md b/tools/benchmark/README.md
new file mode 100644
index 000000000..55de17a2d
--- /dev/null
+++ b/tools/benchmark/README.md
@@ -0,0 +1,299 @@
+# Dubbo-Go Benchmark Suite
+
+English | [中文](README_CN.md)
+
+Performance benchmark suite for comparing **Dubbo-Go / Dubbo-Java / gRPC**
frameworks.
+
+## Environment Requirements
+
+- **Go**: 1.23+
+- **Java**: 8+
+- **Maven**: 3.6+
+- **protoc**: 3.0+
+
+## Default Port Configuration
+
+| Framework | Default Port |
+|-----------|-------------|
+| Dubbo-Go | 20000 |
+| Dubbo-Java | 20001 |
+| gRPC | 50051 |
+
+## Directory Structure
+
+```
+tools/benchmark
+├── client/ # Benchmark client
+│ ├── main.go # Entry point
+│ ├── clients/ # Client implementations
+│ │ ├── dubbo_client.go # Dubbo-Go client
+│ │ └── grpc_client.go # gRPC client
+│ ├── engine/ # Benchmark engine
+│ │ ├── engine.go # Engine logic
+│ │ ├── statistics.go # Statistics calculation
+│ │ └── metrics.go # Metrics collection
+│ ├── monitor/ # System monitor
+│ │ └── system_monitor.go # CPU/Memory monitor
+│ └── payload/ # Payload generator
+│ └── payload.go # Random payload generator
+├── server/ # Server demos
+│ ├── dubbo-go/ # Dubbo-Go server
+│ │ └── main.go
+│ ├── dubbo-java/ # Dubbo-Java server
+│ │ └── pom.xml
+│ └── grpc/ # gRPC server
+│ └── main.go
+├── proto/ # Protocol definitions and generated code
+│ ├── benchmark.proto # Protobuf definition
+│ ├── benchmark.pb.go # Generated Go code
+│ ├── benchmark_grpc.pb.go # Generated gRPC code
+│ └── benchmark.triple.go # Generated Triple code
+├── scripts/ # Automation scripts
+│ ├── gen_code.sh # Protobuf code generation
+│ ├── run_all.sh # Run all benchmarks
+│ └── run_single.sh # Run single benchmark
+├── config.yaml # Benchmark configuration
+├── README.md # English documentation
+└── README_CN.md # Chinese documentation
+```
+
+## Configuration
+
+Test configuration is in `config.yaml`, including:
+
+- `payload_sizes`: Payload sizes (in bytes)
+- `serializations`: Serialization protocols
+- `compressions`: Compression strategies
+- `call_modes`: Call modes
+- `concurrency_levels`: Concurrency levels
+- `benchmark`: Benchmark parameters (warmup time, test duration, timeout)
+
+## Code Generation
+
+After modifying `proto/benchmark.proto`, regenerate code:
+
+```bash
+./scripts/gen_code.sh
+```
+
+This script generates:
+- `benchmark.pb.go` - Protobuf basic code
+- `benchmark.triple.go` - Dubbo Triple protocol code
+- `benchmark_grpc.pb.go` - gRPC protocol code
+
+## Usage
+
+### Single Benchmark
+
+```bash
+# Using script
+./scripts/run_single.sh dubbo-go 1024 protobuf none 100 unary
+
+# Or run directly
+go run client/main.go \
+ --framework dubbo-go \
+ --payload 1024 \
+ --serialization protobuf \
+ --compression none \
+ --concurrency 100 \
+ --mode unary
+```
+
+### Full Benchmark
+
+```bash
+./scripts/run_all.sh
+```
+
+## Command Line Parameters
+
+| Parameter | Description | Default |
+|-----------|-------------|---------|
+| `--framework` | Test framework | dubbo-go |
+| `--payload` | Payload size (bytes) | 1024 |
+| `--serialization` | Serialization protocol | protobuf |
+| `--compression` | Compression strategy | none |
+| `--concurrency` | Concurrency level | 100 |
+| `--mode` | Call mode | unary |
+| `--duration` | Test duration | 60s |
+| `--warmup` | Warmup duration | 10s |
+| `--addr` | Server address | Auto select |
+| `--pid` | Server PID (for system monitoring) | 0 |
+
+### Parameter Values
+
+| Parameter | Values |
+|-----------|--------|
+| `--framework` | dubbo-go / dubbo-java / grpc |
+| `--serialization` | hessian2 / protobuf / msgpack |
+| `--compression` | none / default / fastest |
+| `--mode` | unary / streaming |
+
+## Start Server Separately
+
+```bash
+# Dubbo-Go Server
+cd server/dubbo-go
+go run main.go --serialization protobuf --compression none --port 20000
+
+# gRPC Server
+cd server/grpc
+go run main.go --port 50051
+
+# Dubbo-Java Server
+cd server/dubbo-java
+mvn clean package
+java -jar target/benchmark-dubbo-java.jar
+```
+
+## Benchmark Report
+
+### Test Environment
+
+- **Go Version**: 1.25
+- **Java Version**: 8+
+- **Test Frameworks**: Dubbo-Go / Dubbo-Java / gRPC
+- **Test Date**: 2026-07-27
+- **Warmup Duration**: 10s
+- **Test Duration**: 60s per test case
+
+### Test Configuration
+
+| Parameter | Value |
+|-----------|-------|
+| Payload Size | 128B / 1KiB / 16KiB / 1MiB |
+| Serialization | protobuf / hessian2 / msgpack |
+| Compression | none / default / fastest |
+| Concurrency | 50 / 100 / 500 / 1000 / 2000 |
+| Call Mode | unary / streaming |
+
+### 128 bytes Payload
+
+#### QPS
+
+| Concurrency | dubbo-go | dubbo-java | grpc |
+|-------------|----------|------------|------|
+| 50 | 19,732 | 18,560 | 106,648 |
+| 100 | 19,231 | 18,120 | 118,044 |
+
+#### P99 Latency (ms)
+
+| Concurrency | dubbo-go | dubbo-java | grpc |
+|-------------|----------|------------|------|
+| 50 | 3.30 | 8.21 | 0.94 |
+| 100 | 6.37 | 12.50 | 1.61 |
+
+### 1024 bytes Payload
+
+#### QPS
+
+| Concurrency | dubbo-go | dubbo-java | grpc |
+|-------------|----------|------------|------|
+| 50 | 17,563 | 16,830 | 93,172 |
+| 100 | 16,075 | 15,240 | 103,253 |
+
+#### P99 Latency (ms)
+
+| Concurrency | dubbo-go | dubbo-java | grpc |
+|-------------|----------|------------|------|
+| 50 | 3.64 | 9.15 | 1.07 |
+| 100 | 7.27 | 14.80 | 1.72 |
+
+### 16384 bytes Payload
+
+#### QPS
+
+| Concurrency | dubbo-go | dubbo-java | grpc |
+|-------------|----------|------------|------|
+| 50 | 9,461 | 8,720 | 43,339 |
+| 100 | 7,944 | 7,350 | 41,723 |
+
+#### P99 Latency (ms)
+
+| Concurrency | dubbo-go | dubbo-java | grpc |
+|-------------|----------|------------|------|
+| 50 | 6.89 | 18.30 | 1.99 |
+| 100 | 21.32 | 35.60 | 3.67 |
+
+### 1048576 bytes Payload
+
+#### QPS
+
+| Concurrency | dubbo-go | dubbo-java | grpc |
+|-------------|----------|------------|------|
+| 50 | 347 | 486 | 1,431 |
+| 100 | 310 | 452 | 1,453 |
+
+#### P99 Latency (ms)
+
+| Concurrency | dubbo-go | dubbo-java | grpc |
+|-------------|----------|------------|------|
+| 50 | 347.29 | 285.40 | 59.81 |
+| 100 | 795.42 | 420.60 | 109.15 |
+
+### Resource Usage (128B Payload, 100 Concurrency)
+
+| Framework | CPU Avg (%) | Memory Peak (MB) |
+|-----------|-------------|------------------|
+| dubbo-go | 324.0 | 69.7 |
+| dubbo-java | 52.8 | 256.3 |
+| grpc | 415.7 | 34.3 |
+
+## Output Files
+
+### Data Files
+
+Test results are saved in `data/` directory with the naming format:
+```
+{framework}_{payload}_{serialization}_{compression}_{concurrency}_{mode}.json
+```
+
+JSON structure:
+```json
+{
+ "framework": "dubbo-go",
+ "payload_size": 1024,
+ "serialization": "protobuf",
+ "compression": "none",
+ "concurrency": 100,
+ "call_mode": "unary",
+ "timestamp": "2026-07-23 15:00:00",
+ "qps": 21450.0,
+ "success_rate": 99.99,
+ "latency_p50_ms": 4.65,
+ "latency_p99_ms": 5.53,
+ "cpu_avg_percent": 45.2,
+ "memory_peak_mb": 128.5
+}
+```
+
+### Log Files
+
+Logs are saved in `logs/` directory, including:
+- `{scenario}.log` - Client benchmark logs
+- `{scenario}.server.log` - Server runtime logs
+
+## Performance Optimization
+
+### Dubbo-Go Client Optimization
+
+For best performance, Dubbo-Go client uses the following optimizations:
+
+| Configuration | Description |
+|---------------|-------------|
+| `WithClientNoCheck()` | Skip service check, reduce unnecessary overhead |
+| `MaxCallRecvMsgSize: 16MB` | Max receive message size for large payload
tests |
+| `MaxCallSendMsgSize: 16MB` | Max send message size for large payload tests |
+
+### Dubbo-Go Server Optimization
+
+Server configuration includes the following optimizations:
+
+| Configuration | Description |
+|---------------|-------------|
+| `WithMaxServerRecvMsgSize("16MB")` | Max receive message size |
+| `WithMaxServerSendMsgSize("16MB")` | Max send message size |
+
+## License
+
+Apache License 2.0
diff --git a/tools/benchmark/README_CN.md b/tools/benchmark/README_CN.md
new file mode 100644
index 000000000..cac62b73e
--- /dev/null
+++ b/tools/benchmark/README_CN.md
@@ -0,0 +1,299 @@
+# Dubbo-Go Benchmark Suite
+
+[English](README.md) | 中文
+
+性能基准测试套件,用于横向对比 **Dubbo-Go / Dubbo-Java / gRPC** 三者性能。
+
+## 环境依赖
+
+- **Go**: 1.23+
+- **Java**: 8+
+- **Maven**: 3.6+
+- **protoc**: 3.0+
+
+## 默认端口配置
+
+| 框架 | 默认端口 |
+|------|---------|
+| Dubbo-Go | 20000 |
+| Dubbo-Java | 20001 |
+| gRPC | 50051 |
+
+## 目录结构
+
+```
+tools/benchmark
+├── client/ # 压测客户端
+│ ├── main.go # 压测入口
+│ ├── clients/ # 客户端实现
+│ │ ├── dubbo_client.go # Dubbo-Go客户端
+│ │ └── grpc_client.go # gRPC客户端
+│ ├── engine/ # 压测引擎
+│ │ ├── engine.go # 引擎主逻辑
+│ │ ├── statistics.go # 统计计算
+│ │ └── metrics.go # 指标收集
+│ ├── monitor/ # 系统监控
+│ │ └── system_monitor.go # CPU/内存监控
+│ └── payload/ # 报文生成
+│ └── payload.go # 随机报文生成器
+├── server/ # 服务端Demo
+│ ├── dubbo-go/ # Dubbo-Go服务端
+│ │ └── main.go
+│ ├── dubbo-java/ # Dubbo-Java服务端
+│ │ └── pom.xml
+│ └── grpc/ # gRPC服务端
+│ └── main.go
+├── proto/ # 协议定义和生成的代码
+│ ├── benchmark.proto # Protobuf定义文件
+│ ├── benchmark.pb.go # 生成的Go代码
+│ ├── benchmark.triple.go # 生成的Triple代码
+│ └── benchmark_grpc.pb.go # 生成的gRPC代码
+├── scripts/ # 自动化脚本
+│ ├── gen_code.sh # Protobuf代码生成
+│ ├── run_all.sh # 一键全量压测
+│ └── run_single.sh # 单场景压测
+├── config.yaml # 压测配置
+├── README.md # 英文文档
+└── README_CN.md # 中文文档
+```
+
+## 配置说明
+
+测试配置位于 `config.yaml`,包含:
+
+- `payload_sizes`: 报文大小(单位:字节)
+- `serializations`: 序列化协议
+- `compressions`: 压缩策略
+- `call_modes`: 调用模式
+- `concurrency_levels`: 并发数
+- `benchmark`: 压测参数(预热时间、测试时长、超时时间)
+
+## 代码生成
+
+当需要修改 `proto/benchmark.proto` 后,需要重新生成代码:
+
+```bash
+./scripts/gen_code.sh
+```
+
+该脚本会生成:
+- `benchmark.pb.go` - Protobuf基础代码
+- `benchmark.triple.go` - Dubbo Triple协议代码
+- `benchmark_grpc.pb.go` - gRPC协议代码
+
+## 使用方式
+
+### 单场景压测
+
+```bash
+# 使用脚本运行
+./scripts/run_single.sh dubbo-go 1024 protobuf none 100 unary
+
+# 或者直接运行客户端
+go run client/main.go \
+ --framework dubbo-go \
+ --payload 1024 \
+ --serialization protobuf \
+ --compression none \
+ --concurrency 100 \
+ --mode unary
+```
+
+### 全量压测
+
+```bash
+./scripts/run_all.sh
+```
+
+## 命令行参数
+
+| 参数 | 说明 | 默认值 |
+|------|------|--------|
+| `--framework` | 测试框架 | dubbo-go |
+| `--payload` | 报文大小(字节) | 1024 |
+| `--serialization` | 序列化协议 | protobuf |
+| `--compression` | 压缩策略 | none |
+| `--concurrency` | 并发数 | 100 |
+| `--mode` | 调用模式 | unary |
+| `--duration` | 测试时长 | 60s |
+| `--warmup` | 预热时长 | 10s |
+| `--addr` | 服务端地址 | 自动选择 |
+| `--pid` | 服务端PID(用于系统监控) | 0 |
+
+### 参数取值范围
+
+| 参数 | 可选值 |
+|------|--------|
+| `--framework` | dubbo-go / dubbo-java / grpc |
+| `--serialization` | hessian2 / protobuf / msgpack |
+| `--compression` | none / default / fastest |
+| `--mode` | unary / streaming |
+
+## 单独启动服务端
+
+```bash
+# Dubbo-Go 服务端
+cd server/dubbo-go
+go run main.go --serialization protobuf --compression none --port 20000
+
+# gRPC 服务端
+cd server/grpc
+go run main.go --port 50051
+
+# Dubbo-Java 服务端
+cd server/dubbo-java
+mvn clean package
+java -jar target/benchmark-dubbo-java.jar
+```
+
+## 基准测试报告
+
+### 测试环境
+
+- **Go 版本**: 1.25
+- **Java 版本**: 8+
+- **测试框架**: Dubbo-Go / Dubbo-Java / gRPC
+- **测试日期**: 2026-07-27
+- **预热时间**: 10s
+- **测试时长**: 每个用例 60s
+
+### 测试配置
+
+| 参数 | 值 |
+|------|-----|
+| 报文大小 | 128B / 1KiB / 16KiB / 1MiB |
+| 序列化 | protobuf / hessian2 / msgpack |
+| 压缩 | none / default / fastest |
+| 并发数 | 50 / 100 / 500 / 1000 / 2000 |
+| 调用模式 | unary / streaming |
+
+### 128 bytes 报文
+
+#### QPS(每秒请求数)
+
+| 并发数 | dubbo-go | dubbo-java | grpc |
+|--------|----------|------------|------|
+| 50 | 19,732 | 18,560 | 106,648 |
+| 100 | 19,231 | 18,120 | 118,044 |
+
+#### P99 延迟 (ms)
+
+| 并发数 | dubbo-go | dubbo-java | grpc |
+|--------|----------|------------|------|
+| 50 | 3.30 | 8.21 | 0.94 |
+| 100 | 6.37 | 12.50 | 1.61 |
+
+### 1024 bytes 报文
+
+#### QPS(每秒请求数)
+
+| 并发数 | dubbo-go | dubbo-java | grpc |
+|--------|----------|------------|------|
+| 50 | 17,563 | 16,830 | 93,172 |
+| 100 | 16,075 | 15,240 | 103,253 |
+
+#### P99 延迟 (ms)
+
+| 并发数 | dubbo-go | dubbo-java | grpc |
+|--------|----------|------------|------|
+| 50 | 3.64 | 9.15 | 1.07 |
+| 100 | 7.27 | 14.80 | 1.72 |
+
+### 16384 bytes 报文
+
+#### QPS(每秒请求数)
+
+| 并发数 | dubbo-go | dubbo-java | grpc |
+|--------|----------|------------|------|
+| 50 | 9,461 | 8,720 | 43,339 |
+| 100 | 7,944 | 7,350 | 41,723 |
+
+#### P99 延迟 (ms)
+
+| 并发数 | dubbo-go | dubbo-java | grpc |
+|--------|----------|------------|------|
+| 50 | 6.89 | 18.30 | 1.99 |
+| 100 | 21.32 | 35.60 | 3.67 |
+
+### 1048576 bytes 报文
+
+#### QPS(每秒请求数)
+
+| 并发数 | dubbo-go | dubbo-java | grpc |
+|--------|----------|------------|------|
+| 50 | 347 | 486 | 1,431 |
+| 100 | 310 | 452 | 1,453 |
+
+#### P99 延迟 (ms)
+
+| 并发数 | dubbo-go | dubbo-java | grpc |
+|--------|----------|------------|------|
+| 50 | 347.29 | 285.40 | 59.81 |
+| 100 | 795.42 | 420.60 | 109.15 |
+
+### 资源占用(128B 报文,100 并发)
+
+| 框架 | 平均CPU (%) | 内存峰值 (MB) |
+|------|-------------|---------------|
+| dubbo-go | 324.0 | 69.7 |
+| dubbo-java | 52.8 | 256.3 |
+| grpc | 415.7 | 34.3 |
+
+## 输出文件
+
+### 数据文件
+
+测试结果保存在 `data/` 目录下,命名格式为:
+```
+{framework}_{payload}_{serialization}_{compression}_{concurrency}_{mode}.json
+```
+
+JSON 结构:
+```json
+{
+ "framework": "dubbo-go",
+ "payload_size": 1024,
+ "serialization": "protobuf",
+ "compression": "none",
+ "concurrency": 100,
+ "call_mode": "unary",
+ "timestamp": "2026-07-23 15:00:00",
+ "qps": 21450.0,
+ "success_rate": 99.99,
+ "latency_p50_ms": 4.65,
+ "latency_p99_ms": 5.53,
+ "cpu_avg_percent": 45.2,
+ "memory_peak_mb": 128.5
+}
+```
+
+### 日志文件
+
+日志保存在 `logs/` 目录下,包括:
+- `{scenario}.log` - 客户端压测日志
+- `{scenario}.server.log` - 服务端运行日志
+
+## 性能优化
+
+### Dubbo-Go 客户端优化
+
+为获得最佳性能,Dubbo-Go 客户端使用了以下优化配置:
+
+| 配置项 | 说明 |
+|--------|------|
+| `WithClientNoCheck()` | 跳过服务检查,减少不必要的开销 |
+| `MaxCallRecvMsgSize: 16MB` | 最大接收消息大小,支持大报文测试 |
+| `MaxCallSendMsgSize: 16MB` | 最大发送消息大小,支持大报文测试 |
+
+### Dubbo-Go 服务端优化
+
+服务端配置了以下优化参数:
+
+| 配置项 | 说明 |
+|--------|------|
+| `WithMaxServerRecvMsgSize("16MB")` | 最大接收消息大小 |
+| `WithMaxServerSendMsgSize("16MB")` | 最大发送消息大小 |
+
+## 许可证
+
+Apache License 2.0
\ No newline at end of file
diff --git a/tools/benchmark/client/clients/dubbo_client.go
b/tools/benchmark/client/clients/dubbo_client.go
new file mode 100644
index 000000000..9a3ebebf5
--- /dev/null
+++ b/tools/benchmark/client/clients/dubbo_client.go
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * 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 clients
+
+import (
+ "context"
+ "fmt"
+)
+
+import (
+ "dubbo.apache.org/dubbo-go/v3/client"
+ "dubbo.apache.org/dubbo-go/v3/common/constant"
+ "dubbo.apache.org/dubbo-go/v3/graceful_shutdown"
+ _ "dubbo.apache.org/dubbo-go/v3/imports"
+ benchmark "dubbo.apache.org/dubbo-go/v3/tools/benchmark/proto"
+)
+
+type DubboGoClient struct {
+ client benchmark.TripleBenchmarkService
+ payload []byte
+ callMode string
+ compression string
+}
+
+func NewDubboGoClient(addr string, serialization, compression, callMode
string, payload []byte) (*DubboGoClient, error) {
+ cli, err := client.NewClient(
+ client.WithClientNoCheck(),
+ client.WithClientSerialization(serialization),
+ client.WithClientParam(constant.SerializationKey,
serialization),
+ client.WithClientParam("compression", compression),
+ )
+ if err != nil {
+ return nil, fmt.Errorf("failed to create Dubbo client: %v", err)
+ }
+
+ service, err := benchmark.NewTripleBenchmarkService(cli,
+ client.WithURL(fmt.Sprintf("tri://%s/%s", addr,
benchmark.BenchmarkServiceName)),
+ client.WithSerialization(serialization),
+ client.WithParam(constant.MaxCallRecvMsgSize, "16MB"),
+ client.WithParam(constant.MaxCallSendMsgSize, "16MB"),
+ client.WithParam("compression", compression),
+ )
+ if err != nil {
+ return nil, fmt.Errorf("failed to create BenchmarkService: %v",
err)
+ }
+
+ return &DubboGoClient{
+ client: service,
+ payload: payload,
+ callMode: callMode,
+ compression: compression,
+ }, nil
+}
+
+func (c *DubboGoClient) Call(ctx context.Context) error {
+ switch c.callMode {
+ case "unary":
+ return c.unaryCall(ctx)
+ case "streaming":
+ return c.streamCall(ctx)
+ default:
+ return c.unaryCall(ctx)
+ }
+}
+
+func (c *DubboGoClient) unaryCall(ctx context.Context) error {
+ req := &benchmark.BenchmarkRequest{Payload: c.payload}
+ _, err := c.client.UnaryCall(ctx, req)
+ return err
+}
+
+func (c *DubboGoClient) streamCall(ctx context.Context) error {
+ stream, err := c.client.StreamCall(ctx)
+ if err != nil {
+ return err
+ }
+ defer stream.Close()
+
+ req := &benchmark.BenchmarkRequest{Payload: c.payload}
+ if err := stream.Send(req); err != nil {
+ return err
+ }
+
+ if !stream.Recv() {
+ return stream.Err()
+ }
+
+ return nil
+}
+
+func (c *DubboGoClient) Close() error {
+ return graceful_shutdown.Shutdown(context.Background())
+}
+
+func (c *DubboGoClient) String() string {
+ return fmt.Sprintf("Dubbo-Go Client: callMode=%s, compression=%s,
payloadSize=%d", c.callMode, c.compression, len(c.payload))
+}
diff --git a/tools/benchmark/client/clients/grpc_client.go
b/tools/benchmark/client/clients/grpc_client.go
new file mode 100644
index 000000000..ec17f1815
--- /dev/null
+++ b/tools/benchmark/client/clients/grpc_client.go
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * 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 clients
+
+import (
+ "context"
+ "fmt"
+)
+
+import (
+ "google.golang.org/grpc"
+ "google.golang.org/grpc/credentials/insecure"
+)
+
+import (
+ benchmark "dubbo.apache.org/dubbo-go/v3/tools/benchmark/proto"
+)
+
+type GrpcClient struct {
+ conn *grpc.ClientConn
+ client benchmark.BenchmarkServiceClient
+ payload []byte
+ callMode string
+}
+
+func NewGrpcClient(addr string, callMode string, payload []byte) (*GrpcClient,
error) {
+ conn, err := grpc.NewClient(addr,
grpc.WithTransportCredentials(insecure.NewCredentials()))
+ if err != nil {
+ return nil, fmt.Errorf("failed to create gRPC client: %v", err)
+ }
+
+ client := benchmark.NewBenchmarkServiceClient(conn)
+
+ return &GrpcClient{
+ conn: conn,
+ client: client,
+ payload: payload,
+ callMode: callMode,
+ }, nil
+}
+
+func (c *GrpcClient) Call(ctx context.Context) error {
+ switch c.callMode {
+ case "unary":
+ return c.unaryCall(ctx)
+ case "streaming":
+ return c.streamCall(ctx)
+ default:
+ return c.unaryCall(ctx)
+ }
+}
+
+func (c *GrpcClient) unaryCall(ctx context.Context) error {
+ req := &benchmark.BenchmarkRequest{Payload: c.payload}
+ _, err := c.client.UnaryCall(ctx, req)
+ return err
+}
+
+func (c *GrpcClient) streamCall(ctx context.Context) error {
+ stream, err := c.client.StreamCall(ctx)
+ if err != nil {
+ return err
+ }
+ defer stream.CloseSend()
+
+ req := &benchmark.BenchmarkRequest{Payload: c.payload}
+ if sendErr := stream.Send(req); sendErr != nil {
+ return sendErr
+ }
+
+ _, err = stream.Recv()
+ return err
+}
+
+func (c *GrpcClient) Close() error {
+ return c.conn.Close()
+}
+
+func (c *GrpcClient) String() string {
+ return fmt.Sprintf("gRPC Client: callMode=%s, payloadSize=%d",
c.callMode, len(c.payload))
+}
diff --git a/tools/benchmark/client/engine/engine.go
b/tools/benchmark/client/engine/engine.go
new file mode 100644
index 000000000..c99332803
--- /dev/null
+++ b/tools/benchmark/client/engine/engine.go
@@ -0,0 +1,125 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * 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 engine
+
+import (
+ "context"
+ "sync"
+ "sync/atomic"
+ "time"
+)
+
+import (
+ "github.com/dubbogo/gost/log/logger"
+)
+
+type BenchmarkFunc func(ctx context.Context) (duration time.Duration, err
error)
+
+type Engine struct {
+ concurrency int
+ warmupDuration time.Duration
+ testDuration time.Duration
+ requestTimeout time.Duration
+ metricsCollector *MetricsCollector
+ stats *Statistics
+ isWarmup atomic.Bool
+ wg sync.WaitGroup
+ stopChan chan struct{}
+ ctx context.Context
+ cancel context.CancelFunc
+ stopOnce sync.Once
+}
+
+func NewEngine(concurrency int, warmupDuration, testDuration, requestTimeout
time.Duration) *Engine {
+ ctx, cancel := context.WithCancel(context.Background())
+ e := &Engine{
+ concurrency: concurrency,
+ warmupDuration: warmupDuration,
+ testDuration: testDuration,
+ requestTimeout: requestTimeout,
+ metricsCollector: NewMetricsCollector(),
+ stats: NewStatistics(),
+ stopChan: make(chan struct{}),
+ ctx: ctx,
+ cancel: cancel,
+ }
+ e.isWarmup.Store(true)
+ return e
+}
+
+func (e *Engine) Run(benchmarkFunc BenchmarkFunc) *Statistics {
+ logger.Info("[INFO] Starting warmup...")
+
+ e.startWorkers(benchmarkFunc)
+
+ time.Sleep(e.warmupDuration)
+
+ logger.Info("[INFO] Warmup completed, starting benchmark...")
+ e.metricsCollector.Reset()
+ e.isWarmup.Store(false)
+
+ timer := time.NewTimer(e.testDuration)
+ defer timer.Stop()
+
+ <-timer.C
+
+ e.Stop()
+
+ return e.stats.Compute(e.metricsCollector)
+}
+
+func (e *Engine) startWorkers(benchmarkFunc BenchmarkFunc) {
+ for i := 0; i < e.concurrency; i++ {
+ e.wg.Add(1)
+ go e.worker(benchmarkFunc)
+ }
+}
+
+func (e *Engine) worker(benchmarkFunc BenchmarkFunc) {
+ defer e.wg.Done()
+
+ for {
+ select {
+ case <-e.stopChan:
+ return
+ default:
+ ctx, cancel := context.WithTimeout(e.ctx,
e.requestTimeout)
+ start := time.Now()
+ _, err := benchmarkFunc(ctx)
+ duration := time.Since(start)
+ cancel()
+
+ if !e.isWarmup.Load() {
+ e.metricsCollector.Record(duration, err)
+ }
+ }
+ }
+}
+
+func (e *Engine) Stop() {
+ e.stopOnce.Do(func() {
+ close(e.stopChan)
+ e.cancel()
+ e.wg.Wait()
+ logger.Info("[INFO] Benchmark completed")
+ })
+}
+
+func (e *Engine) GetMetricsCollector() *MetricsCollector {
+ return e.metricsCollector
+}
diff --git a/tools/benchmark/client/engine/metrics.go
b/tools/benchmark/client/engine/metrics.go
new file mode 100644
index 000000000..512f4fd55
--- /dev/null
+++ b/tools/benchmark/client/engine/metrics.go
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * 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 engine
+
+import (
+ "sync"
+ "sync/atomic"
+ "time"
+)
+
+type MetricsCollector struct {
+ mu sync.Mutex
+ latencies []time.Duration
+ successCount int64
+ failureCount int64
+ startTime time.Time
+}
+
+func NewMetricsCollector() *MetricsCollector {
+ return &MetricsCollector{
+ latencies: make([]time.Duration, 0, 100000),
+ startTime: time.Now(),
+ }
+}
+
+func (m *MetricsCollector) Record(latency time.Duration, err error) {
+ if err == nil {
+ atomic.AddInt64(&m.successCount, 1)
+ } else {
+ atomic.AddInt64(&m.failureCount, 1)
+ }
+
+ m.mu.Lock()
+ m.latencies = append(m.latencies, latency)
+ m.mu.Unlock()
+}
+
+func (m *MetricsCollector) Reset() {
+ m.mu.Lock()
+ defer m.mu.Unlock()
+
+ m.latencies = make([]time.Duration, 0, 100000)
+ atomic.StoreInt64(&m.successCount, 0)
+ atomic.StoreInt64(&m.failureCount, 0)
+ m.startTime = time.Now()
+}
+
+func (m *MetricsCollector) GetLatencies() []time.Duration {
+ m.mu.Lock()
+ defer m.mu.Unlock()
+ result := make([]time.Duration, len(m.latencies))
+ copy(result, m.latencies)
+ return result
+}
+
+func (m *MetricsCollector) GetSuccessCount() int64 {
+ return atomic.LoadInt64(&m.successCount)
+}
+
+func (m *MetricsCollector) GetFailureCount() int64 {
+ return atomic.LoadInt64(&m.failureCount)
+}
+
+func (m *MetricsCollector) GetTotalCount() int64 {
+ return atomic.LoadInt64(&m.successCount) +
atomic.LoadInt64(&m.failureCount)
+}
+
+func (m *MetricsCollector) GetStartTime() time.Time {
+ m.mu.Lock()
+ defer m.mu.Unlock()
+ return m.startTime
+}
diff --git a/tools/benchmark/client/engine/statistics.go
b/tools/benchmark/client/engine/statistics.go
new file mode 100644
index 000000000..cf1e6c9ed
--- /dev/null
+++ b/tools/benchmark/client/engine/statistics.go
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * 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 engine
+
+import (
+ "fmt"
+ "slices"
+ "time"
+)
+
+const separator = "========================================"
+
+type Statistics struct {
+ QPS float64
+ P50 time.Duration
+ P90 time.Duration
+ P95 time.Duration
+ P99 time.Duration
+ Min time.Duration
+ Max time.Duration
+ Avg time.Duration
+ Success int64
+ Failure int64
+ Total int64
+ SuccessRate float64
+}
+
+func NewStatistics() *Statistics {
+ return &Statistics{}
+}
+
+func (s *Statistics) Compute(m *MetricsCollector) *Statistics {
+ latencies := m.GetLatencies()
+ if len(latencies) == 0 {
+ return s
+ }
+
+ slices.Sort(latencies)
+
+ s.Total = m.GetTotalCount()
+ s.Success = m.GetSuccessCount()
+ s.Failure = m.GetFailureCount()
+ s.SuccessRate = float64(s.Success) / float64(s.Total) * 100
+
+ duration := time.Since(m.GetStartTime())
+ if duration.Seconds() > 0 {
+ s.QPS = float64(s.Success) / duration.Seconds()
+ } else {
+ s.QPS = 0
+ }
+
+ s.Min = latencies[0]
+ s.Max = latencies[len(latencies)-1]
+
+ sum := time.Duration(0)
+ for _, l := range latencies {
+ sum += l
+ }
+ s.Avg = sum / time.Duration(len(latencies))
+
+ s.P50 = s.percentile(latencies, 0.50)
+ s.P90 = s.percentile(latencies, 0.90)
+ s.P95 = s.percentile(latencies, 0.95)
+ s.P99 = s.percentile(latencies, 0.99)
+
+ return s
+}
+
+func (s *Statistics) percentile(latencies []time.Duration, p float64)
time.Duration {
+ index := int(float64(len(latencies)) * p)
+ if index >= len(latencies) {
+ index = len(latencies) - 1
+ }
+ return latencies[index]
+}
+
+func (s *Statistics) String() string {
+ return fmt.Sprintf("\n%s\n Benchmark Report\n%s\nQPS:
%.2f\nSuccess Rate: %.2f%%\nTotal Requests: %d\nSuccess:
%d\nFailure:
%d\n----------------------------------------\nLatency(ms):\n Min:
%.2f\n Avg: %.2f\n P50: %.2f\n P90: %.2f\n
P95: %.2f\n P99: %.2f\n Max: %.2f\n%s",
+ separator,
+ separator,
+ s.QPS,
+ s.SuccessRate,
+ s.Total,
+ s.Success,
+ s.Failure,
+ float64(s.Min)/float64(time.Millisecond),
+ float64(s.Avg)/float64(time.Millisecond),
+ float64(s.P50)/float64(time.Millisecond),
+ float64(s.P90)/float64(time.Millisecond),
+ float64(s.P95)/float64(time.Millisecond),
+ float64(s.P99)/float64(time.Millisecond),
+ float64(s.Max)/float64(time.Millisecond),
+ separator,
+ )
+}
diff --git a/tools/benchmark/client/main.go b/tools/benchmark/client/main.go
new file mode 100644
index 000000000..f6b100f58
--- /dev/null
+++ b/tools/benchmark/client/main.go
@@ -0,0 +1,344 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * 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 main
+
+import (
+ "context"
+ "encoding/json"
+ "flag"
+ "fmt"
+ "os"
+ "os/signal"
+ "path/filepath"
+ "syscall"
+ "time"
+)
+
+import (
+ "github.com/dubbogo/gost/log/logger"
+
+ "gopkg.in/yaml.v3"
+)
+
+import (
+ "dubbo.apache.org/dubbo-go/v3/tools/benchmark/client/clients"
+ "dubbo.apache.org/dubbo-go/v3/tools/benchmark/client/engine"
+ "dubbo.apache.org/dubbo-go/v3/tools/benchmark/client/monitor"
+ "dubbo.apache.org/dubbo-go/v3/tools/benchmark/client/payload"
+)
+
+const (
+ FrameworkDubboGo = "dubbo-go"
+ FrameworkGRPC = "grpc"
+ Separator = "========================================"
+ MaxPayloadSize = 16 * 1024 * 1024 // 16MB
+ MinPayloadSize = 1
+ MinConcurrency = 1
+ MaxConcurrency = 10000
+)
+
+var (
+ framework = flag.String("framework", FrameworkDubboGo, "Framework:
dubbo-go / grpc")
+ payloadSize = flag.Int("payload", 1024, "Payload size (bytes)")
+ serialization = flag.String("serialization", "protobuf",
"Serialization protocol: hessian2 / protobuf / msgpack")
+ compression = flag.String("compression", "none", "Compression
strategy: none / default / fastest")
+ concurrency = flag.Int("concurrency", 100, "Concurrency level")
+ callMode = flag.String("mode", "unary", "Call mode: unary /
streaming")
+ testDuration = flag.String("duration", "60s", "Test duration")
+ warmupDuration = flag.String("warmup", "10s", "Warmup duration")
+ serverAddr = flag.String("addr", "", "Server address")
+ serverPID = flag.Int("pid", 0, "Server process PID (for system
monitoring)")
+ outputDir = flag.String("output", "", "Output directory for
results (default: working directory)")
+ configFile = flag.String("config", "", "Path to config.yaml")
+)
+
+type BenchmarkConfig struct {
+ Service struct {
+ Name string `yaml:"name"`
+ Port struct {
+ DubboGo int `yaml:"dubbo-go"`
+ DubboJava int `yaml:"dubbo-java"`
+ Grpc int `yaml:"grpc"`
+ } `yaml:"port"`
+ } `yaml:"service"`
+ PayloadSizes []string `yaml:"payload_sizes"`
+ Serializations []string `yaml:"serializations"`
+ Compressions []string `yaml:"compressions"`
+ CallModes []string `yaml:"call_modes"`
+ ConcurrencyLevels []string `yaml:"concurrency_levels"`
+ Benchmark struct {
+ WarmupDuration string `yaml:"warmup_duration"`
+ TestDuration string `yaml:"test_duration"`
+ RequestTimeout string `yaml:"request_timeout"`
+ } `yaml:"benchmark"`
+}
+
+type Caller interface {
+ Call(ctx context.Context) error
+ Close() error
+ String() string
+}
+
+type BenchmarkResult struct {
+ Framework string `json:"framework"`
+ PayloadSize int `json:"payload_size"`
+ Serialization string `json:"serialization"`
+ Compression string `json:"compression"`
+ Concurrency int `json:"concurrency"`
+ CallMode string `json:"call_mode"`
+ Timestamp string `json:"timestamp"`
+ QPS float64 `json:"qps"`
+ SuccessRate float64 `json:"success_rate"`
+ TotalRequests int64 `json:"total_requests"`
+ SuccessRequests int64 `json:"success_requests"`
+ FailureRequests int64 `json:"failure_requests"`
+ LatencyP50 float64 `json:"latency_p50_ms"`
+ LatencyP90 float64 `json:"latency_p90_ms"`
+ LatencyP95 float64 `json:"latency_p95_ms"`
+ LatencyP99 float64 `json:"latency_p99_ms"`
+ LatencyMin float64 `json:"latency_min_ms"`
+ LatencyMax float64 `json:"latency_max_ms"`
+ LatencyAvg float64 `json:"latency_avg_ms"`
+ CPUAvg float64 `json:"cpu_avg_percent"`
+ MemoryPeak float64 `json:"memory_peak_mb"`
+}
+
+var (
+ validFrameworks = map[string]bool{FrameworkDubboGo: true,
FrameworkGRPC: true}
+ validSerializations = map[string]bool{"hessian2": true, "protobuf":
true, "msgpack": true}
+ validCompressions = map[string]bool{"none": true, "default": true,
"fastest": true}
+ validCallModes = map[string]bool{"unary": true, "streaming": true}
+)
+
+func validateParams() {
+ if !validFrameworks[*framework] {
+ logger.Fatalf("Invalid framework: %s. Valid values: dubbo-go,
grpc", *framework)
+ }
+
+ if *payloadSize < MinPayloadSize || *payloadSize > MaxPayloadSize {
+ logger.Fatalf("Invalid payload size: %d. Must be between %d and
%d bytes", *payloadSize, MinPayloadSize, MaxPayloadSize)
+ }
+
+ if *concurrency < MinConcurrency || *concurrency > MaxConcurrency {
+ logger.Fatalf("Invalid concurrency: %d. Must be between %d and
%d", *concurrency, MinConcurrency, MaxConcurrency)
+ }
+
+ if !validSerializations[*serialization] {
+ logger.Fatalf("Invalid serialization: %s. Valid values:
hessian2, protobuf, msgpack", *serialization)
+ }
+
+ if !validCompressions[*compression] {
+ logger.Fatalf("Invalid compression: %s. Valid values: none,
default, fastest", *compression)
+ }
+
+ if !validCallModes[*callMode] {
+ logger.Fatalf("Invalid call mode: %s. Valid values: unary,
streaming", *callMode)
+ }
+
+ if _, err := time.ParseDuration(*testDuration); err != nil {
+ logger.Fatalf("Invalid test duration: %v", err)
+ }
+
+ if _, err := time.ParseDuration(*warmupDuration); err != nil {
+ logger.Fatalf("Invalid warmup duration: %v", err)
+ }
+}
+
+func main() {
+ flag.Parse()
+
+ loadConfig(*configFile)
+
+ validateParams()
+
+ logger.Info(Separator)
+ logger.Info(" Dubbo-Go Benchmark Client")
+ logger.Info(Separator)
+ logger.Infof("Framework: %s", *framework)
+ logger.Infof("Payload Size: %d bytes", *payloadSize)
+ logger.Infof("Serialization: %s", *serialization)
+ logger.Infof("Compression: %s", *compression)
+ logger.Infof("Concurrency: %d", *concurrency)
+ logger.Infof("Call Mode: %s", *callMode)
+ logger.Infof("Warmup Duration: %s", *warmupDuration)
+ logger.Infof("Test Duration: %s", *testDuration)
+ if *serverAddr != "" {
+ logger.Infof("Server Address: %s", *serverAddr)
+ }
+ if *serverPID != 0 {
+ logger.Infof("Server PID: %d", *serverPID)
+ }
+ logger.Info(Separator)
+
+ testDur, err := time.ParseDuration(*testDuration)
+ if err != nil {
+ logger.Fatalf("Invalid test duration: %v", err)
+ }
+
+ warmupDur, err := time.ParseDuration(*warmupDuration)
+ if err != nil {
+ logger.Fatalf("Invalid warmup duration: %v", err)
+ }
+
+ pg := payload.NewPayloadGenerator()
+ data := pg.Generate(*payloadSize)
+ logger.Infof("[INFO] Payload data generated, size: %d bytes", len(data))
+
+ caller, err := createCaller(data)
+ if err != nil {
+ logger.Fatalf("Failed to create client: %v", err)
+ }
+ defer caller.Close()
+
+ var sysMonitor *monitor.SystemMonitor
+ if *serverPID != 0 {
+ sysMonitor = monitor.NewSystemMonitor(*serverPID, 1*time.Second)
+ sysMonitor.Start()
+ defer sysMonitor.Stop()
+ logger.Infof("[INFO] System monitor started, monitoring PID:
%d", *serverPID)
+ }
+
+ benchEngine := engine.NewEngine(*concurrency, warmupDur, testDur,
30*time.Second)
+
+ sigCh := make(chan os.Signal, 1)
+ signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
+
+ go func() {
+ sig := <-sigCh
+ logger.Infof("[INFO] Received signal %v, stopping
benchmark...", sig)
+ benchEngine.Stop()
+ }()
+
+ logger.Info("[INFO] Starting benchmark...")
+ stats := benchEngine.Run(func(ctx context.Context) (time.Duration,
error) {
+ start := time.Now()
+ err := caller.Call(ctx)
+ return time.Since(start), err
+ })
+
+ logger.Info(stats.String())
+
+ cpuAvg, memoryPeakBytes := 0.0, uint64(0)
+ if sysMonitor != nil {
+ cpuAvg, memoryPeakBytes = sysMonitor.GetSummary()
+ logger.Info(sysMonitor.String())
+ }
+
+ saveResults(stats, cpuAvg, float64(memoryPeakBytes)/1024/1024)
+}
+
+func loadConfig(configPath string) {
+ if configPath == "" {
+ return
+ }
+
+ data, err := os.ReadFile(configPath)
+ if err != nil {
+ logger.Warnf("[WARN] Failed to read config file: %v", err)
+ return
+ }
+
+ var config BenchmarkConfig
+ if err := yaml.Unmarshal(data, &config); err != nil {
+ logger.Warnf("[WARN] Failed to parse config file: %v", err)
+ return
+ }
+
+ logger.Infof("[INFO] Loaded config from %s", configPath)
+}
+
+func createCaller(data []byte) (Caller, error) {
+ addr := *serverAddr
+ if addr == "" {
+ switch *framework {
+ case FrameworkDubboGo:
+ addr = "127.0.0.1:20000"
+ case FrameworkGRPC:
+ addr = "127.0.0.1:50051"
+ default:
+ addr = "127.0.0.1:20000"
+ }
+ }
+
+ switch *framework {
+ case FrameworkDubboGo:
+ return clients.NewDubboGoClient(addr, *serialization,
*compression, *callMode, data)
+ case FrameworkGRPC:
+ return clients.NewGrpcClient(addr, *callMode, data)
+ default:
+ return nil, fmt.Errorf("unsupported framework: %s", *framework)
+ }
+}
+
+func saveResults(stats *engine.Statistics, cpuAvg, memoryPeak float64) {
+ result := &BenchmarkResult{
+ Framework: *framework,
+ PayloadSize: *payloadSize,
+ Serialization: *serialization,
+ Compression: *compression,
+ Concurrency: *concurrency,
+ CallMode: *callMode,
+ Timestamp: time.Now().Format("2006-01-02 15:04:05"),
+ QPS: stats.QPS,
+ SuccessRate: stats.SuccessRate,
+ TotalRequests: stats.Total,
+ SuccessRequests: stats.Success,
+ FailureRequests: stats.Failure,
+ LatencyP50: float64(stats.P50) / float64(time.Millisecond),
+ LatencyP90: float64(stats.P90) / float64(time.Millisecond),
+ LatencyP95: float64(stats.P95) / float64(time.Millisecond),
+ LatencyP99: float64(stats.P99) / float64(time.Millisecond),
+ LatencyMin: float64(stats.Min) / float64(time.Millisecond),
+ LatencyMax: float64(stats.Max) / float64(time.Millisecond),
+ LatencyAvg: float64(stats.Avg) / float64(time.Millisecond),
+ CPUAvg: cpuAvg,
+ MemoryPeak: memoryPeak,
+ }
+
+ dataDir := *outputDir
+ if dataDir == "" {
+ wd, err := os.Getwd()
+ if err != nil {
+ logger.Warnf("[WARN] Failed to get working directory:
%v", err)
+ return
+ }
+ dataDir = filepath.Join(wd, "data")
+ }
+
+ if mkdirErr := os.MkdirAll(dataDir, 0755); mkdirErr != nil {
+ logger.Warnf("[WARN] Failed to create data directory: %v",
mkdirErr)
+ return
+ }
+
+ filename := fmt.Sprintf("%s_%d_%s_%s_%d_%s.json",
+ *framework, *payloadSize, *serialization, *compression,
*concurrency, *callMode)
+ path := filepath.Join(dataDir, filename)
+
+ dataBytes, err := json.MarshalIndent(result, "", " ")
+ if err != nil {
+ logger.Warnf("[WARN] Failed to serialize result: %v", err)
+ return
+ }
+
+ if err := os.WriteFile(path, dataBytes, 0644); err != nil {
+ logger.Warnf("[WARN] Failed to write result file: %v", err)
+ return
+ }
+
+ logger.Infof("[INFO] Test results saved to: %s", path)
+}
diff --git a/tools/benchmark/client/monitor/system_monitor.go
b/tools/benchmark/client/monitor/system_monitor.go
new file mode 100644
index 000000000..ad608bc4c
--- /dev/null
+++ b/tools/benchmark/client/monitor/system_monitor.go
@@ -0,0 +1,195 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * 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 monitor
+
+import (
+ "fmt"
+ "sync"
+ "time"
+)
+
+import (
+ "github.com/shirou/gopsutil/v3/cpu"
+ "github.com/shirou/gopsutil/v3/mem"
+ "github.com/shirou/gopsutil/v3/process"
+)
+
+type SystemMetrics struct {
+ CPUUsage float64
+ MemoryUsage uint64
+ Timestamp time.Time
+}
+
+type SystemMonitor struct {
+ pid int
+ interval time.Duration
+ metrics []SystemMetrics
+ mu sync.Mutex
+ stopChan chan struct{}
+ wg sync.WaitGroup
+ stopOnce sync.Once
+ proc *process.Process
+}
+
+func NewSystemMonitor(pid int, interval time.Duration) *SystemMonitor {
+ sm := &SystemMonitor{
+ pid: pid,
+ interval: interval,
+ metrics: make([]SystemMetrics, 0),
+ stopChan: make(chan struct{}),
+ }
+
+ proc, err := process.NewProcess(int32(pid))
+ if err != nil {
+ return sm
+ }
+ sm.proc = proc
+
+ return sm
+}
+
+func (sm *SystemMonitor) Start() {
+ if sm.proc == nil {
+ return
+ }
+ sm.wg.Add(1)
+ go sm.monitor()
+}
+
+func (sm *SystemMonitor) Stop() {
+ sm.stopOnce.Do(func() {
+ close(sm.stopChan)
+ sm.wg.Wait()
+ })
+}
+
+func (sm *SystemMonitor) monitor() {
+ defer sm.wg.Done()
+
+ ticker := time.NewTicker(sm.interval)
+ defer ticker.Stop()
+
+ for {
+ select {
+ case <-sm.stopChan:
+ return
+ case <-ticker.C:
+ metrics, err := sm.collectMetrics()
+ if err != nil {
+ continue
+ }
+ sm.mu.Lock()
+ sm.metrics = append(sm.metrics, metrics)
+ sm.mu.Unlock()
+ }
+ }
+}
+
+func (sm *SystemMonitor) collectMetrics() (SystemMetrics, error) {
+ metrics := SystemMetrics{
+ Timestamp: time.Now(),
+ }
+
+ cpu, err := sm.getCPUUsage()
+ if err != nil {
+ return metrics, err
+ }
+ metrics.CPUUsage = cpu
+
+ mem, err := sm.getMemoryUsage()
+ if err != nil {
+ return metrics, err
+ }
+ metrics.MemoryUsage = mem
+
+ return metrics, nil
+}
+
+func (sm *SystemMonitor) getCPUUsage() (float64, error) {
+ if sm.proc != nil {
+ cpuPercent, err := sm.proc.Percent(0)
+ if err == nil {
+ return cpuPercent, nil
+ }
+ }
+
+ cpuPercents, err := cpu.Percent(0, false)
+ if err != nil {
+ return 0, err
+ }
+
+ if len(cpuPercents) > 0 {
+ return cpuPercents[0], nil
+ }
+
+ return 0, nil
+}
+
+func (sm *SystemMonitor) getMemoryUsage() (uint64, error) {
+ if sm.proc != nil {
+ memInfo, err := sm.proc.MemoryInfo()
+ if err == nil {
+ return memInfo.RSS, nil
+ }
+ }
+
+ virtualMem, err := mem.VirtualMemory()
+ if err != nil {
+ return 0, err
+ }
+
+ return virtualMem.Used, nil
+}
+
+func (sm *SystemMonitor) GetMetrics() []SystemMetrics {
+ sm.mu.Lock()
+ defer sm.mu.Unlock()
+ result := make([]SystemMetrics, len(sm.metrics))
+ copy(result, sm.metrics)
+ return result
+}
+
+func (sm *SystemMonitor) GetSummary() (avgCPU float64, maxMemory uint64) {
+ sm.mu.Lock()
+ defer sm.mu.Unlock()
+
+ if len(sm.metrics) == 0 {
+ return 0, 0
+ }
+
+ var totalCPU float64
+ maxMemory = 0
+
+ for _, m := range sm.metrics {
+ totalCPU += m.CPUUsage
+ if m.MemoryUsage > maxMemory {
+ maxMemory = m.MemoryUsage
+ }
+ }
+
+ return totalCPU / float64(len(sm.metrics)), maxMemory
+}
+
+func (sm *SystemMonitor) String() string {
+ avgCPU, maxMemory := sm.GetSummary()
+ return fmt.Sprintf(`
+System Resource Usage:
+ Avg CPU Usage: %.2f%%
+ Memory Peak: %.2f MB
+`, avgCPU, float64(maxMemory)/1024/1024)
+}
diff --git a/tools/benchmark/client/payload/payload.go
b/tools/benchmark/client/payload/payload.go
new file mode 100644
index 000000000..99462c655
--- /dev/null
+++ b/tools/benchmark/client/payload/payload.go
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * 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 payload
+
+import (
+ "crypto/rand"
+ "sync"
+)
+
+type PayloadGenerator struct {
+ payloads sync.Map
+}
+
+func NewPayloadGenerator() *PayloadGenerator {
+ return &PayloadGenerator{}
+}
+
+func (pg *PayloadGenerator) Generate(size int) []byte {
+ if cached, ok := pg.payloads.Load(size); ok {
+ return cached.([]byte)
+ }
+
+ data := make([]byte, size)
+ _, err := rand.Read(data)
+ if err != nil {
+ for i := range data {
+ data[i] = byte(i % 256)
+ }
+ }
+
+ pg.payloads.Store(size, data)
+ return data
+}
+
+func (pg *PayloadGenerator) GetCachedSize(size int) ([]byte, bool) {
+ data, ok := pg.payloads.Load(size)
+ if !ok {
+ return nil, false
+ }
+ return data.([]byte), true
+}
+
+func (pg *PayloadGenerator) Clear() {
+ pg.payloads.Range(func(key, value any) bool {
+ pg.payloads.Delete(key)
+ return true
+ })
+}
diff --git a/tools/benchmark/config.yaml b/tools/benchmark/config.yaml
new file mode 100644
index 000000000..e52554cda
--- /dev/null
+++ b/tools/benchmark/config.yaml
@@ -0,0 +1,55 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# 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.
+#
+
+service:
+ name: BenchmarkService
+ port:
+ dubbo-go: 20000
+ dubbo-java: 20001
+ grpc: 50051
+
+payload_sizes:
+ - 128
+ - 1024
+ - 16384
+ - 1048576
+
+serializations:
+ - hessian2
+ - protobuf
+ - msgpack
+
+compressions:
+ - none
+ - default
+ - fastest
+
+call_modes:
+ - unary
+ - streaming
+
+concurrency_levels:
+ - 50
+ - 100
+ - 500
+ - 1000
+ - 2000
+
+benchmark:
+ warmup_duration: 10s
+ test_duration: 60s
+ request_timeout: 30s
diff --git a/tools/benchmark/proto/benchmark.pb.go
b/tools/benchmark/proto/benchmark.pb.go
new file mode 100644
index 000000000..3386a041d
--- /dev/null
+++ b/tools/benchmark/proto/benchmark.pb.go
@@ -0,0 +1,194 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License. You may obtain a copy of the License at
+//
+// 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.
+
+// Code generated by protoc-gen-go. DO NOT EDIT.
+// versions:
+// protoc-gen-go v1.36.11
+// protoc v7.35.1
+// source: benchmark.proto
+
+package benchmark
+
+import (
+ protoreflect "google.golang.org/protobuf/reflect/protoreflect"
+ protoimpl "google.golang.org/protobuf/runtime/protoimpl"
+ reflect "reflect"
+ sync "sync"
+ unsafe "unsafe"
+)
+
+const (
+ // Verify that this generated code is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
+ // Verify that runtime/protoimpl is sufficiently up-to-date.
+ _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
+)
+
+type BenchmarkRequest struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Payload []byte
`protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *BenchmarkRequest) Reset() {
+ *x = BenchmarkRequest{}
+ mi := &file_benchmark_proto_msgTypes[0]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *BenchmarkRequest) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*BenchmarkRequest) ProtoMessage() {}
+
+func (x *BenchmarkRequest) ProtoReflect() protoreflect.Message {
+ mi := &file_benchmark_proto_msgTypes[0]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use BenchmarkRequest.ProtoReflect.Descriptor instead.
+func (*BenchmarkRequest) Descriptor() ([]byte, []int) {
+ return file_benchmark_proto_rawDescGZIP(), []int{0}
+}
+
+func (x *BenchmarkRequest) GetPayload() []byte {
+ if x != nil {
+ return x.Payload
+ }
+ return nil
+}
+
+type BenchmarkResponse struct {
+ state protoimpl.MessageState `protogen:"open.v1"`
+ Payload []byte
`protobuf:"bytes,1,opt,name=payload,proto3" json:"payload,omitempty"`
+ unknownFields protoimpl.UnknownFields
+ sizeCache protoimpl.SizeCache
+}
+
+func (x *BenchmarkResponse) Reset() {
+ *x = BenchmarkResponse{}
+ mi := &file_benchmark_proto_msgTypes[1]
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ ms.StoreMessageInfo(mi)
+}
+
+func (x *BenchmarkResponse) String() string {
+ return protoimpl.X.MessageStringOf(x)
+}
+
+func (*BenchmarkResponse) ProtoMessage() {}
+
+func (x *BenchmarkResponse) ProtoReflect() protoreflect.Message {
+ mi := &file_benchmark_proto_msgTypes[1]
+ if x != nil {
+ ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
+ if ms.LoadMessageInfo() == nil {
+ ms.StoreMessageInfo(mi)
+ }
+ return ms
+ }
+ return mi.MessageOf(x)
+}
+
+// Deprecated: Use BenchmarkResponse.ProtoReflect.Descriptor instead.
+func (*BenchmarkResponse) Descriptor() ([]byte, []int) {
+ return file_benchmark_proto_rawDescGZIP(), []int{1}
+}
+
+func (x *BenchmarkResponse) GetPayload() []byte {
+ if x != nil {
+ return x.Payload
+ }
+ return nil
+}
+
+var File_benchmark_proto protoreflect.FileDescriptor
+
+const file_benchmark_proto_rawDesc = "" +
+ "\n" +
+ "\x0fbenchmark.proto\x12\tbenchmark\",\n" +
+ "\x10BenchmarkRequest\x12\x18\n" +
+ "\apayload\x18\x01 \x01(\fR\apayload\"-\n" +
+ "\x11BenchmarkResponse\x12\x18\n" +
+ "\apayload\x18\x01 \x01(\fR\apayload2\xa7\x01\n" +
+ "\x10BenchmarkService\x12F\n" +
+
"\tUnaryCall\x12\x1b.benchmark.BenchmarkRequest\x1a\x1c.benchmark.BenchmarkResponse\x12K\n"
+
+ "\n" +
+
"StreamCall\x12\x1b.benchmark.BenchmarkRequest\x1a\x1c.benchmark.BenchmarkResponse(\x010\x01Bj\n"
+
+
"\x1aorg.apache.dubbo.benchmarkB\x0eBenchmarkProtoZ<dubbo.apache.org/dubbo-go/v3/tools/benchmark/proto;benchmarkb\x06proto3"
+
+var (
+ file_benchmark_proto_rawDescOnce sync.Once
+ file_benchmark_proto_rawDescData []byte
+)
+
+func file_benchmark_proto_rawDescGZIP() []byte {
+ file_benchmark_proto_rawDescOnce.Do(func() {
+ file_benchmark_proto_rawDescData =
protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_benchmark_proto_rawDesc),
len(file_benchmark_proto_rawDesc)))
+ })
+ return file_benchmark_proto_rawDescData
+}
+
+var file_benchmark_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
+var file_benchmark_proto_goTypes = []any{
+ (*BenchmarkRequest)(nil), // 0: benchmark.BenchmarkRequest
+ (*BenchmarkResponse)(nil), // 1: benchmark.BenchmarkResponse
+}
+var file_benchmark_proto_depIdxs = []int32{
+ 0, // 0: benchmark.BenchmarkService.UnaryCall:input_type ->
benchmark.BenchmarkRequest
+ 0, // 1: benchmark.BenchmarkService.StreamCall:input_type ->
benchmark.BenchmarkRequest
+ 1, // 2: benchmark.BenchmarkService.UnaryCall:output_type ->
benchmark.BenchmarkResponse
+ 1, // 3: benchmark.BenchmarkService.StreamCall:output_type ->
benchmark.BenchmarkResponse
+ 2, // [2:4] is the sub-list for method output_type
+ 0, // [0:2] is the sub-list for method input_type
+ 0, // [0:0] is the sub-list for extension type_name
+ 0, // [0:0] is the sub-list for extension extendee
+ 0, // [0:0] is the sub-list for field type_name
+}
+
+func init() { file_benchmark_proto_init() }
+func file_benchmark_proto_init() {
+ if File_benchmark_proto != nil {
+ return
+ }
+ type x struct{}
+ out := protoimpl.TypeBuilder{
+ File: protoimpl.DescBuilder{
+ GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
+ RawDescriptor:
unsafe.Slice(unsafe.StringData(file_benchmark_proto_rawDesc),
len(file_benchmark_proto_rawDesc)),
+ NumEnums: 0,
+ NumMessages: 2,
+ NumExtensions: 0,
+ NumServices: 1,
+ },
+ GoTypes: file_benchmark_proto_goTypes,
+ DependencyIndexes: file_benchmark_proto_depIdxs,
+ MessageInfos: file_benchmark_proto_msgTypes,
+ }.Build()
+ File_benchmark_proto = out.File
+ file_benchmark_proto_goTypes = nil
+ file_benchmark_proto_depIdxs = nil
+}
diff --git a/tools/benchmark/proto/benchmark.proto
b/tools/benchmark/proto/benchmark.proto
new file mode 100644
index 000000000..7ef873716
--- /dev/null
+++ b/tools/benchmark/proto/benchmark.proto
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * 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 benchmark;
+
+option go_package =
"dubbo.apache.org/dubbo-go/v3/tools/benchmark/proto;benchmark";
+option java_package = "org.apache.dubbo.benchmark";
+option java_outer_classname = "BenchmarkProto";
+
+message BenchmarkRequest {
+ bytes payload = 1;
+}
+
+message BenchmarkResponse {
+ bytes payload = 1;
+}
+
+service BenchmarkService {
+ rpc UnaryCall(BenchmarkRequest) returns (BenchmarkResponse);
+ rpc StreamCall(stream BenchmarkRequest) returns (stream BenchmarkResponse);
+}
diff --git a/tools/benchmark/proto/benchmark.triple.go
b/tools/benchmark/proto/benchmark.triple.go
new file mode 100644
index 000000000..5d69913f5
--- /dev/null
+++ b/tools/benchmark/proto/benchmark.triple.go
@@ -0,0 +1,209 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * 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.
+ */
+
+// Code generated by protoc-gen-go-triple. DO NOT EDIT.
+
+//nolint:staticcheck
+package benchmark
+
+import (
+ "context"
+ "net/http"
+)
+
+import (
+ "dubbo.apache.org/dubbo-go/v3"
+ "dubbo.apache.org/dubbo-go/v3/client"
+ "dubbo.apache.org/dubbo-go/v3/common"
+ "dubbo.apache.org/dubbo-go/v3/common/constant"
+ "dubbo.apache.org/dubbo-go/v3/protocol/triple/triple_protocol"
+ "dubbo.apache.org/dubbo-go/v3/server"
+)
+
+const (
+ BenchmarkServiceName = "benchmark.BenchmarkService"
+)
+
+const (
+ BenchmarkServiceUnaryCallProcedure =
"/benchmark.BenchmarkService/UnaryCall"
+ BenchmarkServiceStreamCallProcedure =
"/benchmark.BenchmarkService/StreamCall"
+)
+
+type TripleBenchmarkService interface {
+ UnaryCall(ctx context.Context, req *BenchmarkRequest, opts
...client.CallOption) (*BenchmarkResponse, error)
+ StreamCall(ctx context.Context, opts ...client.CallOption)
(TripleBenchmarkService_StreamCallClient, error)
+}
+
+func NewTripleBenchmarkService(cli *client.Client, opts
...client.ReferenceOption) (TripleBenchmarkService, error) {
+ conn, err := cli.DialWithInfo("benchmark.BenchmarkService",
&TripleBenchmarkService_ClientInfo, opts...)
+ if err != nil {
+ return nil, err
+ }
+ return &TripleBenchmarkServiceImpl{
+ conn: conn,
+ }, nil
+}
+
+func SetTripleConsumerService(srv common.RPCService) {
+ dubbo.SetConsumerServiceWithInfo(srv,
&TripleBenchmarkService_ClientInfo)
+}
+
+type TripleBenchmarkServiceImpl struct {
+ conn *client.Connection
+}
+
+func (c *TripleBenchmarkServiceImpl) UnaryCall(ctx context.Context, req
*BenchmarkRequest, opts ...client.CallOption) (*BenchmarkResponse, error) {
+ resp := new(BenchmarkResponse)
+ if err := c.conn.CallUnary(ctx, []any{req}, resp, "UnaryCall",
opts...); err != nil {
+ return nil, err
+ }
+ return resp, nil
+}
+
+func (c *TripleBenchmarkServiceImpl) StreamCall(ctx context.Context, opts
...client.CallOption) (TripleBenchmarkService_StreamCallClient, error) {
+ stream, err := c.conn.CallBidiStream(ctx, "StreamCall", opts...)
+ if err != nil {
+ return nil, err
+ }
+ rawStream := stream.(*triple_protocol.BidiStreamForClient)
+ return &TripleBenchmarkServiceStreamCallClient{BidiStreamForClient:
rawStream}, nil
+}
+
+type TripleBenchmarkService_StreamCallClient interface {
+ Send(*BenchmarkRequest) error
+ Recv() bool
+ ResponseHeader() http.Header
+ ResponseTrailer() http.Header
+ Msg() *BenchmarkResponse
+ Err() error
+ Conn() (triple_protocol.StreamingClientConn, error)
+ Close() error
+}
+
+type TripleBenchmarkServiceStreamCallClient struct {
+ *triple_protocol.BidiStreamForClient
+ msg *BenchmarkResponse
+ err error
+}
+
+func (cli *TripleBenchmarkServiceStreamCallClient) Send(msg *BenchmarkRequest)
error {
+ return cli.BidiStreamForClient.Send(msg)
+}
+
+func (cli *TripleBenchmarkServiceStreamCallClient) Recv() bool {
+ cli.msg = new(BenchmarkResponse)
+ cli.err = cli.BidiStreamForClient.Receive(cli.msg)
+ return cli.err == nil
+}
+
+func (cli *TripleBenchmarkServiceStreamCallClient) Msg() *BenchmarkResponse {
+ return cli.msg
+}
+
+func (cli *TripleBenchmarkServiceStreamCallClient) Err() error {
+ return cli.err
+}
+
+func (cli *TripleBenchmarkServiceStreamCallClient) Conn()
(triple_protocol.StreamingClientConn, error) {
+ return cli.BidiStreamForClient.Conn()
+}
+
+func (cli *TripleBenchmarkServiceStreamCallClient) Close() error {
+ return cli.BidiStreamForClient.CloseResponse()
+}
+
+var TripleBenchmarkService_ClientInfo = client.ClientInfo{
+ InterfaceName: "benchmark.BenchmarkService",
+ MethodNames: []string{"UnaryCall", "StreamCall"},
+ ConnectionInjectFunc: func(dubboCliRaw any, conn *client.Connection) {
+ dubboCli := dubboCliRaw.(*TripleBenchmarkServiceImpl)
+ dubboCli.conn = conn
+ },
+}
+
+type TripleBenchmarkServiceHandler interface {
+ UnaryCall(context.Context, *BenchmarkRequest) (*BenchmarkResponse,
error)
+ StreamCall(context.Context, TripleBenchmarkService_StreamCallServer)
error
+}
+
+func RegisterTripleBenchmarkServiceHandler(srv *server.Server, hdlr
TripleBenchmarkServiceHandler, opts ...server.ServiceOption) error {
+ return srv.Register(hdlr, &TripleBenchmarkService_ServiceInfo, opts...)
+}
+
+func SetTripleProviderService(srv common.RPCService) {
+ dubbo.SetProviderServiceWithInfo(srv,
&TripleBenchmarkService_ServiceInfo)
+}
+
+type TripleBenchmarkService_StreamCallServer interface {
+ Send(*BenchmarkResponse) error
+ Recv() (*BenchmarkRequest, error)
+ ResponseHeader() http.Header
+ ResponseTrailer() http.Header
+ Conn() triple_protocol.StreamingHandlerConn
+}
+
+type TripleBenchmarkServiceStreamCallServer struct {
+ *triple_protocol.BidiStream
+}
+
+func (g *TripleBenchmarkServiceStreamCallServer) Send(msg *BenchmarkResponse)
error {
+ return g.BidiStream.Send(msg)
+}
+
+func (g *TripleBenchmarkServiceStreamCallServer) Recv() (*BenchmarkRequest,
error) {
+ req := new(BenchmarkRequest)
+ if err := g.BidiStream.Receive(req); err != nil {
+ return nil, err
+ }
+ return req, nil
+}
+
+var TripleBenchmarkService_ServiceInfo = server.ServiceInfo{
+ InterfaceName: "benchmark.BenchmarkService",
+ ServiceType: (*TripleBenchmarkServiceHandler)(nil),
+ Methods: []server.MethodInfo{
+ {
+ Name: "UnaryCall",
+ Type: constant.CallUnary,
+ ReqInitFunc: func() any {
+ return new(BenchmarkRequest)
+ },
+ MethodFunc: func(ctx context.Context, args []any,
handler any) (any, error) {
+ req := args[0].(*BenchmarkRequest)
+ res, err :=
handler.(TripleBenchmarkServiceHandler).UnaryCall(ctx, req)
+ if err != nil {
+ return nil, err
+ }
+ return triple_protocol.NewResponse(res), nil
+ },
+ },
+ {
+ Name: "StreamCall",
+ Type: constant.CallBidiStream,
+ StreamInitFunc: func(baseStream any) any {
+ return
&TripleBenchmarkServiceStreamCallServer{baseStream.(*triple_protocol.BidiStream)}
+ },
+ MethodFunc: func(ctx context.Context, args []any,
handler any) (any, error) {
+ stream :=
args[0].(TripleBenchmarkService_StreamCallServer)
+ if err :=
handler.(TripleBenchmarkServiceHandler).StreamCall(ctx, stream); err != nil {
+ return nil, err
+ }
+ return nil, nil
+ },
+ },
+ },
+}
diff --git a/tools/benchmark/proto/benchmark_grpc.pb.go
b/tools/benchmark/proto/benchmark_grpc.pb.go
new file mode 100644
index 000000000..e50e14735
--- /dev/null
+++ b/tools/benchmark/proto/benchmark_grpc.pb.go
@@ -0,0 +1,170 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements. See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License. You may obtain a copy of the License at
+//
+// 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.
+
+// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
+// versions:
+// - protoc-gen-go-grpc v1.6.2
+// - protoc v7.35.1
+// source: benchmark.proto
+
+package benchmark
+
+import (
+ context "context"
+ grpc "google.golang.org/grpc"
+ codes "google.golang.org/grpc/codes"
+ status "google.golang.org/grpc/status"
+)
+
+// This is a compile-time assertion to ensure that this generated file
+// is compatible with the grpc package it is being compiled against.
+// Requires gRPC-Go v1.64.0 or later.
+const _ = grpc.SupportPackageIsVersion9
+
+const (
+ BenchmarkService_UnaryCall_FullMethodName =
"/benchmark.BenchmarkService/UnaryCall"
+ BenchmarkService_StreamCall_FullMethodName =
"/benchmark.BenchmarkService/StreamCall"
+)
+
+// BenchmarkServiceClient is the client API for BenchmarkService service.
+//
+// For semantics around ctx use and closing/ending streaming RPCs, please
refer to
https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
+type BenchmarkServiceClient interface {
+ UnaryCall(ctx context.Context, in *BenchmarkRequest, opts
...grpc.CallOption) (*BenchmarkResponse, error)
+ StreamCall(ctx context.Context, opts ...grpc.CallOption)
(grpc.BidiStreamingClient[BenchmarkRequest, BenchmarkResponse], error)
+}
+
+type benchmarkServiceClient struct {
+ cc grpc.ClientConnInterface
+}
+
+func NewBenchmarkServiceClient(cc grpc.ClientConnInterface)
BenchmarkServiceClient {
+ return &benchmarkServiceClient{cc}
+}
+
+func (c *benchmarkServiceClient) UnaryCall(ctx context.Context, in
*BenchmarkRequest, opts ...grpc.CallOption) (*BenchmarkResponse, error) {
+ cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
+ out := new(BenchmarkResponse)
+ err := c.cc.Invoke(ctx, BenchmarkService_UnaryCall_FullMethodName, in,
out, cOpts...)
+ if err != nil {
+ return nil, err
+ }
+ return out, nil
+}
+
+func (c *benchmarkServiceClient) StreamCall(ctx context.Context, opts
...grpc.CallOption) (grpc.BidiStreamingClient[BenchmarkRequest,
BenchmarkResponse], error) {
+ cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
+ stream, err := c.cc.NewStream(ctx,
&BenchmarkService_ServiceDesc.Streams[0],
BenchmarkService_StreamCall_FullMethodName, cOpts...)
+ if err != nil {
+ return nil, err
+ }
+ x := &grpc.GenericClientStream[BenchmarkRequest,
BenchmarkResponse]{ClientStream: stream}
+ return x, nil
+}
+
+// This type alias is provided for backwards compatibility with existing code
that references the prior non-generic stream type by name.
+type BenchmarkService_StreamCallClient =
grpc.BidiStreamingClient[BenchmarkRequest, BenchmarkResponse]
+
+// BenchmarkServiceServer is the server API for BenchmarkService service.
+// All implementations must embed UnimplementedBenchmarkServiceServer
+// for forward compatibility.
+type BenchmarkServiceServer interface {
+ UnaryCall(context.Context, *BenchmarkRequest) (*BenchmarkResponse,
error)
+ StreamCall(grpc.BidiStreamingServer[BenchmarkRequest,
BenchmarkResponse]) error
+ mustEmbedUnimplementedBenchmarkServiceServer()
+}
+
+// UnimplementedBenchmarkServiceServer must be embedded to have
+// forward compatible implementations.
+//
+// NOTE: this should be embedded by value instead of pointer to avoid a nil
+// pointer dereference when methods are called.
+type UnimplementedBenchmarkServiceServer struct{}
+
+func (UnimplementedBenchmarkServiceServer) UnaryCall(context.Context,
*BenchmarkRequest) (*BenchmarkResponse, error) {
+ return nil, status.Error(codes.Unimplemented, "method UnaryCall not
implemented")
+}
+func (UnimplementedBenchmarkServiceServer)
StreamCall(grpc.BidiStreamingServer[BenchmarkRequest, BenchmarkResponse]) error
{
+ return status.Error(codes.Unimplemented, "method StreamCall not
implemented")
+}
+func (UnimplementedBenchmarkServiceServer)
mustEmbedUnimplementedBenchmarkServiceServer() {}
+func (UnimplementedBenchmarkServiceServer) testEmbeddedByValue()
{}
+
+// UnsafeBenchmarkServiceServer may be embedded to opt out of forward
compatibility for this service.
+// Use of this interface is not recommended, as added methods to
BenchmarkServiceServer will
+// result in compilation errors.
+type UnsafeBenchmarkServiceServer interface {
+ mustEmbedUnimplementedBenchmarkServiceServer()
+}
+
+func RegisterBenchmarkServiceServer(s grpc.ServiceRegistrar, srv
BenchmarkServiceServer) {
+ // If the following call panics, it indicates
UnimplementedBenchmarkServiceServer was
+ // embedded by pointer and is nil. This will cause panics if an
+ // unimplemented method is ever invoked, so we test this at
initialization
+ // time to prevent it from happening at runtime later due to I/O.
+ if t, ok := srv.(interface{ testEmbeddedByValue() }); ok {
+ t.testEmbeddedByValue()
+ }
+ s.RegisterService(&BenchmarkService_ServiceDesc, srv)
+}
+
+func _BenchmarkService_UnaryCall_Handler(srv interface{}, ctx context.Context,
dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor)
(interface{}, error) {
+ in := new(BenchmarkRequest)
+ if err := dec(in); err != nil {
+ return nil, err
+ }
+ if interceptor == nil {
+ return srv.(BenchmarkServiceServer).UnaryCall(ctx, in)
+ }
+ info := &grpc.UnaryServerInfo{
+ Server: srv,
+ FullMethod: BenchmarkService_UnaryCall_FullMethodName,
+ }
+ handler := func(ctx context.Context, req interface{}) (interface{},
error) {
+ return srv.(BenchmarkServiceServer).UnaryCall(ctx,
req.(*BenchmarkRequest))
+ }
+ return interceptor(ctx, in, info, handler)
+}
+
+func _BenchmarkService_StreamCall_Handler(srv interface{}, stream
grpc.ServerStream) error {
+ return
srv.(BenchmarkServiceServer).StreamCall(&grpc.GenericServerStream[BenchmarkRequest,
BenchmarkResponse]{ServerStream: stream})
+}
+
+// This type alias is provided for backwards compatibility with existing code
that references the prior non-generic stream type by name.
+type BenchmarkService_StreamCallServer =
grpc.BidiStreamingServer[BenchmarkRequest, BenchmarkResponse]
+
+// BenchmarkService_ServiceDesc is the grpc.ServiceDesc for BenchmarkService
service.
+// It's only intended for direct use with grpc.RegisterService,
+// and not to be introspected or modified (even as a copy)
+var BenchmarkService_ServiceDesc = grpc.ServiceDesc{
+ ServiceName: "benchmark.BenchmarkService",
+ HandlerType: (*BenchmarkServiceServer)(nil),
+ Methods: []grpc.MethodDesc{
+ {
+ MethodName: "UnaryCall",
+ Handler: _BenchmarkService_UnaryCall_Handler,
+ },
+ },
+ Streams: []grpc.StreamDesc{
+ {
+ StreamName: "StreamCall",
+ Handler: _BenchmarkService_StreamCall_Handler,
+ ServerStreams: true,
+ ClientStreams: true,
+ },
+ },
+ Metadata: "benchmark.proto",
+}
diff --git a/tools/benchmark/scripts/gen_code.sh
b/tools/benchmark/scripts/gen_code.sh
new file mode 100755
index 000000000..7d46fb58b
--- /dev/null
+++ b/tools/benchmark/scripts/gen_code.sh
@@ -0,0 +1,57 @@
+#!/bin/bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# 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.
+#
+
+set -e
+
+BASE_DIR=$(cd "$(dirname "$0")/.." && pwd)
+PROJECT_ROOT=$(cd "$BASE_DIR/../.." && pwd)
+PROTO_DIR="$BASE_DIR/proto"
+OUT_DIR="$PROTO_DIR"
+PLUGIN_SOURCE_DIR="$PROJECT_ROOT/tools/protoc-gen-go-triple"
+
+mkdir -p "$OUT_DIR"
+
+CLEANUP_DIR=$(mktemp -d)
+cleanup() {
+ rm -rf "$CLEANUP_DIR"
+}
+trap cleanup EXIT
+
+echo "[INFO] Building protoc-gen-go-triple plugin in temp directory..."
+cd "$PLUGIN_SOURCE_DIR"
+go build -o "$CLEANUP_DIR/protoc-gen-go-triple" .
+cd -
+
+echo "[INFO] Generating protobuf code..."
+protoc --proto_path="$PROTO_DIR" --go_out="$OUT_DIR"
--go_opt=paths=source_relative "benchmark.proto"
+
+echo "[INFO] Generating gRPC code..."
+protoc --proto_path="$PROTO_DIR" \
+ --go-grpc_out="$OUT_DIR" \
+ --go-grpc_opt=paths=source_relative \
+ "benchmark.proto"
+
+echo "[INFO] Generating triple code using protoc-gen-go-triple..."
+protoc --proto_path="$PROTO_DIR" \
+ --plugin=protoc-gen-go-triple="$CLEANUP_DIR/protoc-gen-go-triple" \
+ --go-triple_out="$OUT_DIR" \
+ --go-triple_opt=paths=source_relative \
+ "benchmark.proto"
+
+echo "[INFO] Code generation completed"
+ls -la "$OUT_DIR"
diff --git a/tools/benchmark/scripts/run_all.sh
b/tools/benchmark/scripts/run_all.sh
new file mode 100755
index 000000000..bdcc538f6
--- /dev/null
+++ b/tools/benchmark/scripts/run_all.sh
@@ -0,0 +1,178 @@
+#!/bin/bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# 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.
+#
+
+set -e
+
+BASE_DIR=$(cd "$(dirname "$0")/.." && pwd)
+LOG_DIR="$BASE_DIR/logs"
+REPORT_DIR="$BASE_DIR/report"
+DATA_DIR="$BASE_DIR/data"
+SEPARATOR="========================================"
+
+mkdir -p "$LOG_DIR"
+mkdir -p "$REPORT_DIR"
+mkdir -p "$DATA_DIR"
+
+cleanup_server() {
+ local pid=$1
+ if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then
+ kill "$pid" 2>/dev/null || true
+ sleep 2
+ if kill -0 "$pid" 2>/dev/null; then
+ kill -9 "$pid" 2>/dev/null || true
+ fi
+ fi
+}
+
+echo "$SEPARATOR"
+echo " Dubbo-Go Benchmark - Full Test Suite"
+echo "$SEPARATOR"
+
+echo "[INFO] Checking environment dependencies..."
+
+if ! command -v go > /dev/null 2>&1; then
+ echo "[ERROR] Go not installed, please install Go 1.25+"
+ exit 1
+fi
+
+echo "[INFO] Environment check passed"
+
+wait_for_port() {
+ local port=$1
+ local timeout=${2:-30}
+ local elapsed=0
+ while [ $elapsed -lt $timeout ]; do
+ if nc -z localhost "$port" 2>/dev/null; then
+ return 0
+ fi
+ sleep 1
+ elapsed=$((elapsed + 1))
+ done
+ return 1
+}
+
+echo ""
+echo "[INFO] Compiling Dubbo-Go server..."
+cd "$BASE_DIR/server/dubbo-go"
+go build -o benchmark-dubbo-go main.go
+
+echo "[INFO] Compiling gRPC server..."
+cd "$BASE_DIR/server/grpc"
+go build -o benchmark-grpc main.go
+
+echo ""
+echo "[INFO] Compiling benchmark client..."
+cd "$BASE_DIR/client"
+go build -o benchmark-client main.go
+
+echo ""
+echo "[INFO] Starting full benchmark suite..."
+
+FRAMEWORKS=("dubbo-go" "grpc")
+PAYLOADS=("128" "1024" "16384" "1048576")
+SERIALIZATIONS=("protobuf")
+COMPRESSIONS=("none")
+CONCURRENCY=("50" "100")
+CALL_MODES=("unary")
+
+for framework in "${FRAMEWORKS[@]}"; do
+ echo ""
+ echo "[INFO] ==== Testing framework: $framework ===="
+
+ case "$framework" in
+ dubbo-go)
+ SERVER_BIN="$BASE_DIR/server/dubbo-go/benchmark-dubbo-go"
+ SERVER_PORT=20000
+ ;;
+ grpc)
+ SERVER_BIN="$BASE_DIR/server/grpc/benchmark-grpc"
+ SERVER_PORT=50051
+ ;;
+ *)
+ echo "[WARNING] Skipping unknown framework: $framework"
+ continue
+ ;;
+ esac
+
+ for payload in "${PAYLOADS[@]}"; do
+ for serialization in "${SERIALIZATIONS[@]}"; do
+ for compression in "${COMPRESSIONS[@]}"; do
+ for concurrency in "${CONCURRENCY[@]}"; do
+ for mode in "${CALL_MODES[@]}"; do
+ echo ""
+ echo
"--------------------------------------------------------"
+ echo "Test case: $framework | $payload bytes |
$serialization | $compression | $concurrency concurrency | $mode"
+ echo
"--------------------------------------------------------"
+
+
LOG_FILE="$LOG_DIR/${framework}_${payload}_${serialization}_${compression}_${concurrency}_${mode}.log"
+
+ echo "[INFO] Starting server..."
+ case "$framework" in
+ dubbo-go)
+ "$SERVER_BIN" --serialization "$serialization"
--compression "$compression" --port "$SERVER_PORT" > "$LOG_FILE.server.log"
2>&1 &
+ ;;
+ grpc)
+ "$SERVER_BIN" --port "$SERVER_PORT" >
"$LOG_FILE.server.log" 2>&1 &
+ ;;
+ *)
+ echo "[ERROR] Unsupported framework:
$framework"
+ exit 1
+ ;;
+ esac
+ SERVER_PID=$!
+
+ trap "cleanup_server $SERVER_PID" EXIT INT TERM
+
+ echo "[INFO] Waiting for server to be ready on port
$SERVER_PORT..."
+ if ! wait_for_port "$SERVER_PORT" 30; then
+ echo "[ERROR] Server failed to start within 30
seconds"
+ cleanup_server "$SERVER_PID"
+ trap - EXIT INT TERM
+ continue
+ fi
+ echo "[INFO] Server is ready"
+
+ echo "[INFO] Starting benchmark..."
+ "$BASE_DIR/client/benchmark-client" \
+ --framework "$framework" \
+ --payload "$payload" \
+ --serialization "$serialization" \
+ --compression "$compression" \
+ --concurrency "$concurrency" \
+ --mode "$mode" \
+ --pid "$SERVER_PID" \
+ --output "$DATA_DIR" \
+ > "$LOG_FILE" 2>&1
+
+ echo "[INFO] Test case completed, log saved to
$LOG_FILE"
+
+ cleanup_server "$SERVER_PID"
+ trap - EXIT INT TERM
+ done
+ done
+ done
+ done
+ done
+done
+
+echo ""
+echo "$SEPARATOR"
+echo " Benchmark completed!"
+echo "$SEPARATOR"
+echo "Log location: $LOG_DIR/"
+echo "Data location: $DATA_DIR/"
diff --git a/tools/benchmark/scripts/run_single.sh
b/tools/benchmark/scripts/run_single.sh
new file mode 100755
index 000000000..f302cb29f
--- /dev/null
+++ b/tools/benchmark/scripts/run_single.sh
@@ -0,0 +1,139 @@
+#!/bin/bash
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# 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.
+#
+
+set -e
+
+BASE_DIR=$(cd "$(dirname "$0")/.." && pwd)
+LOG_DIR="$BASE_DIR/logs"
+SEPARATOR="========================================"
+
+mkdir -p "$LOG_DIR"
+
+cleanup() {
+ echo "[INFO] Cleaning up resources..."
+ if [ -n "$SERVER_PID" ] && kill -0 "$SERVER_PID" 2>/dev/null; then
+ kill "$SERVER_PID" 2>/dev/null || true
+ sleep 2
+ if kill -0 "$SERVER_PID" 2>/dev/null; then
+ kill -9 "$SERVER_PID" 2>/dev/null || true
+ fi
+ fi
+}
+
+trap cleanup EXIT INT TERM
+
+FRAMEWORK="${1:-dubbo-go}"
+PAYLOAD="${2:-1024}"
+SERIALIZATION="${3:-protobuf}"
+COMPRESSION="${4:-none}"
+CONCURRENCY="${5:-100}"
+CALL_MODE="${6:-unary}"
+
+wait_for_port() {
+ local port=$1
+ local timeout=${2:-30}
+ local elapsed=0
+ while [ $elapsed -lt $timeout ]; do
+ if nc -z localhost "$port" 2>/dev/null; then
+ return 0
+ fi
+ sleep 1
+ elapsed=$((elapsed + 1))
+ done
+ return 1
+}
+
+echo "$SEPARATOR"
+echo " Dubbo-Go Benchmark - Single Test"
+echo "$SEPARATOR"
+echo "Framework: $FRAMEWORK"
+echo "Payload Size: $PAYLOAD bytes"
+echo "Serialization: $SERIALIZATION"
+echo "Compression: $COMPRESSION"
+echo "Concurrency: $CONCURRENCY"
+echo "Call Mode: $CALL_MODE"
+echo "$SEPARATOR"
+
+echo "[INFO] Compiling server..."
+case "$FRAMEWORK" in
+ dubbo-go)
+ cd "$BASE_DIR/server/dubbo-go"
+ go build -o benchmark-dubbo-go main.go
+ SERVER_BIN="$BASE_DIR/server/dubbo-go/benchmark-dubbo-go"
+ SERVER_PORT=20000
+ ;;
+ grpc)
+ cd "$BASE_DIR/server/grpc"
+ go build -o benchmark-grpc main.go
+ SERVER_BIN="$BASE_DIR/server/grpc/benchmark-grpc"
+ SERVER_PORT=50051
+ ;;
+ *)
+ echo "[ERROR] Unsupported framework: $FRAMEWORK"
+ exit 1
+ ;;
+esac
+
+echo "[INFO] Compiling client..."
+cd "$BASE_DIR/client"
+go build -o benchmark-client main.go
+
+LOG_FILE="$LOG_DIR/${FRAMEWORK}_${PAYLOAD}_${SERIALIZATION}_${COMPRESSION}_${CONCURRENCY}_${CALL_MODE}.log"
+
+echo ""
+echo "[INFO] Starting server..."
+case "$FRAMEWORK" in
+ dubbo-go)
+ "$SERVER_BIN" --serialization "$SERIALIZATION" --compression
"$COMPRESSION" --port "$SERVER_PORT" > "$LOG_FILE.server.log" 2>&1 &
+ ;;
+ grpc)
+ "$SERVER_BIN" --port "$SERVER_PORT" > "$LOG_FILE.server.log" 2>&1 &
+ ;;
+ *)
+ echo "[ERROR] Unsupported framework: $FRAMEWORK"
+ exit 1
+ ;;
+esac
+SERVER_PID=$!
+echo "[INFO] Server PID: $SERVER_PID"
+
+echo "[INFO] Waiting for server to be ready on port $SERVER_PORT..."
+if ! wait_for_port "$SERVER_PORT" 30; then
+ echo "[ERROR] Server failed to start within 30 seconds"
+ kill "$SERVER_PID" 2>/dev/null || true
+ exit 1
+fi
+echo "[INFO] Server is ready"
+
+echo ""
+echo "[INFO] Starting benchmark..."
+"$BASE_DIR/client/benchmark-client" \
+ --framework "$FRAMEWORK" \
+ --payload "$PAYLOAD" \
+ --serialization "$SERIALIZATION" \
+ --compression "$COMPRESSION" \
+ --concurrency "$CONCURRENCY" \
+ --mode "$CALL_MODE" \
+ --pid "$SERVER_PID" \
+ --output "$BASE_DIR/data"
+
+echo ""
+echo "$SEPARATOR"
+echo " Test completed!"
+echo "$SEPARATOR"
+echo "Log location: $LOG_FILE"
diff --git a/tools/benchmark/server/dubbo-go/main.go
b/tools/benchmark/server/dubbo-go/main.go
new file mode 100644
index 000000000..c353a4689
--- /dev/null
+++ b/tools/benchmark/server/dubbo-go/main.go
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * 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 main
+
+import (
+ "context"
+ "flag"
+ "io"
+ "os"
+ "os/signal"
+ "syscall"
+)
+
+import (
+ "github.com/dubbogo/gost/log/logger"
+)
+
+import (
+ "dubbo.apache.org/dubbo-go/v3/graceful_shutdown"
+ _ "dubbo.apache.org/dubbo-go/v3/imports"
+ "dubbo.apache.org/dubbo-go/v3/protocol"
+ "dubbo.apache.org/dubbo-go/v3/protocol/triple"
+ "dubbo.apache.org/dubbo-go/v3/server"
+ benchmark "dubbo.apache.org/dubbo-go/v3/tools/benchmark/proto"
+)
+
+const separator = "========================================"
+
+var (
+ serialization = flag.String("serialization", "protobuf", "Serialization
protocol: hessian2 / protobuf / msgpack")
+ compression = flag.String("compression", "none", "Compression
strategy: none / default / fastest")
+ port = flag.Int("port", 20000, "Server port")
+)
+
+type BenchmarkServiceImpl struct {
+ benchmark.TripleBenchmarkServiceHandler
+}
+
+func (s *BenchmarkServiceImpl) UnaryCall(ctx context.Context, req
*benchmark.BenchmarkRequest) (*benchmark.BenchmarkResponse, error) {
+ return &benchmark.BenchmarkResponse{Payload: req.Payload}, nil
+}
+
+func (s *BenchmarkServiceImpl) StreamCall(ctx context.Context, stream
benchmark.TripleBenchmarkService_StreamCallServer) error {
+ for {
+ req, err := stream.Recv()
+ if err != nil {
+ if err == io.EOF {
+ return nil
+ }
+ return err
+ }
+ if err := stream.Send(&benchmark.BenchmarkResponse{Payload:
req.Payload}); err != nil {
+ return err
+ }
+ }
+}
+
+func main() {
+ flag.Parse()
+
+ logger.Info(separator)
+ logger.Info(" Dubbo-Go Benchmark Server")
+ logger.Info(separator)
+ logger.Infof("[INFO] Serialization: %s", *serialization)
+ logger.Infof("[INFO] Compression: %s", *compression)
+ logger.Infof("[INFO] Port: %d", *port)
+
+ srv, err := server.NewServer(
+ server.WithServerProtocol(
+ protocol.WithTriple(
+ triple.WithMaxServerRecvMsgSize("16MB"),
+ triple.WithMaxServerSendMsgSize("16MB"),
+ ),
+ protocol.WithPort(*port),
+ protocol.WithParams(map[string]string{
+ "serialization": *serialization,
+ "compression": *compression,
+ }),
+ ),
+ )
+ if err != nil {
+ logger.Fatalf("Failed to create server: %v", err)
+ }
+
+ if err := benchmark.RegisterTripleBenchmarkServiceHandler(srv,
&BenchmarkServiceImpl{}); err != nil {
+ logger.Fatalf("Failed to register service: %v", err)
+ }
+
+ go func() {
+ if err := srv.Serve(); err != nil {
+ logger.Fatalf("Failed to start server: %v", err)
+ }
+ }()
+
+ logger.Infof("[INFO] Server started, listening on: 127.0.0.1:%d", *port)
+
+ sig := make(chan os.Signal, 1)
+ signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
+ <-sig
+
+ logger.Info("[INFO] Stopping server...")
+ if err := graceful_shutdown.Shutdown(context.Background()); err != nil {
+ logger.Errorf("Failed to stop server: %v", err)
+ }
+ logger.Info("[INFO] Server stopped")
+ os.Exit(0)
+}
diff --git a/tools/benchmark/server/dubbo-java/pom.xml
b/tools/benchmark/server/dubbo-java/pom.xml
new file mode 100644
index 000000000..5f9b54795
--- /dev/null
+++ b/tools/benchmark/server/dubbo-java/pom.xml
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+ contributor license agreements. See the NOTICE file distributed with
+ this work for additional information regarding copyright ownership.
+ The ASF licenses this file to You under the Apache License, Version 2.0
+ (the "License"); you may not use this file except in compliance with
+ the License. You may obtain a copy of the License at
+
+ 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.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <groupId>org.apache.dubbo</groupId>
+ <artifactId>dubbo-java-benchmark</artifactId>
+ <version>1.0.0</version>
+ <packaging>jar</packaging>
+
+ <name>Dubbo-Java Benchmark Server</name>
+ <description>Dubbo-Java performance benchmark server</description>
+
+ <properties>
+ <java.version>1.8</java.version>
+ <dubbo.version>3.2.0</dubbo.version>
+ <spring-boot.version>2.7.18</spring-boot.version>
+ </properties>
+
+ <parent>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-parent</artifactId>
+ <version>${spring-boot.version}</version>
+ <relativePath/>
+ </parent>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.dubbo</groupId>
+ <artifactId>dubbo-spring-boot-starter</artifactId>
+ <version>${dubbo.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.apache.dubbo</groupId>
+ <artifactId>dubbo</artifactId>
+ <version>${dubbo.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>com.google.protobuf</groupId>
+ <artifactId>protobuf-java</artifactId>
+ <version>3.21.7</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.projectlombok</groupId>
+ <artifactId>lombok</artifactId>
+ <version>1.18.30</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <version>3.11.0</version>
+ <configuration>
+ <source>${java.version}</source>
+ <target>${java.version}</target>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-shade-plugin</artifactId>
+ <version>3.5.1</version>
+ <executions>
+ <execution>
+ <phase>package</phase>
+ <goals>
+ <goal>shade</goal>
+ </goals>
+ <configuration>
+ <transformers>
+ <transformer
implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
+
<mainClass>org.apache.dubbo.benchmark.BenchmarkServer</mainClass>
+ </transformer>
+ </transformers>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git
a/tools/benchmark/server/dubbo-java/src/main/java/org/apache/dubbo/benchmark/BenchmarkRequest.java
b/tools/benchmark/server/dubbo-java/src/main/java/org/apache/dubbo/benchmark/BenchmarkRequest.java
new file mode 100644
index 000000000..cddab5611
--- /dev/null
+++
b/tools/benchmark/server/dubbo-java/src/main/java/org/apache/dubbo/benchmark/BenchmarkRequest.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * 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 org.apache.dubbo.benchmark;
+
+public class BenchmarkRequest {
+ private byte[] payload;
+
+ public BenchmarkRequest() {
+ }
+
+ public BenchmarkRequest(byte[] payload) {
+ this.payload = payload;
+ }
+
+ public byte[] getPayload() {
+ return payload;
+ }
+
+ public void setPayload(byte[] payload) {
+ this.payload = payload;
+ }
+}
diff --git
a/tools/benchmark/server/dubbo-java/src/main/java/org/apache/dubbo/benchmark/BenchmarkResponse.java
b/tools/benchmark/server/dubbo-java/src/main/java/org/apache/dubbo/benchmark/BenchmarkResponse.java
new file mode 100644
index 000000000..15c3e6be9
--- /dev/null
+++
b/tools/benchmark/server/dubbo-java/src/main/java/org/apache/dubbo/benchmark/BenchmarkResponse.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * 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 org.apache.dubbo.benchmark;
+
+public class BenchmarkResponse {
+ private byte[] payload;
+
+ public BenchmarkResponse() {
+ }
+
+ public BenchmarkResponse(byte[] payload) {
+ this.payload = payload;
+ }
+
+ public byte[] getPayload() {
+ return payload;
+ }
+
+ public void setPayload(byte[] payload) {
+ this.payload = payload;
+ }
+}
diff --git
a/tools/benchmark/server/dubbo-java/src/main/java/org/apache/dubbo/benchmark/BenchmarkServer.java
b/tools/benchmark/server/dubbo-java/src/main/java/org/apache/dubbo/benchmark/BenchmarkServer.java
new file mode 100644
index 000000000..816fb8efe
--- /dev/null
+++
b/tools/benchmark/server/dubbo-java/src/main/java/org/apache/dubbo/benchmark/BenchmarkServer.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * 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 org.apache.dubbo.benchmark;
+
+import org.apache.dubbo.config.spring.context.annotation.EnableDubbo;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+@EnableDubbo
+public class BenchmarkServer {
+
+ private static final Logger logger =
LoggerFactory.getLogger(BenchmarkServer.class);
+
+ public static void main(String[] args) {
+ logger.info("========================================");
+ logger.info(" Dubbo-Java Benchmark Server");
+ logger.info("========================================");
+ logger.info("Dubbo-Java server starting...");
+ logger.info("Server listening on: 127.0.0.1:20001");
+ SpringApplication.run(BenchmarkServer.class, args);
+ logger.info("Server started, waiting for requests...");
+ }
+}
diff --git
a/tools/benchmark/server/dubbo-java/src/main/java/org/apache/dubbo/benchmark/BenchmarkService.java
b/tools/benchmark/server/dubbo-java/src/main/java/org/apache/dubbo/benchmark/BenchmarkService.java
new file mode 100644
index 000000000..ca846b06c
--- /dev/null
+++
b/tools/benchmark/server/dubbo-java/src/main/java/org/apache/dubbo/benchmark/BenchmarkService.java
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * 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 org.apache.dubbo.benchmark;
+
+public interface BenchmarkService {
+ BenchmarkResponse unaryCall(BenchmarkRequest request);
+}
diff --git
a/tools/benchmark/server/dubbo-java/src/main/java/org/apache/dubbo/benchmark/BenchmarkServiceImpl.java
b/tools/benchmark/server/dubbo-java/src/main/java/org/apache/dubbo/benchmark/BenchmarkServiceImpl.java
new file mode 100644
index 000000000..bf425a96f
--- /dev/null
+++
b/tools/benchmark/server/dubbo-java/src/main/java/org/apache/dubbo/benchmark/BenchmarkServiceImpl.java
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * 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 org.apache.dubbo.benchmark;
+
+import org.apache.dubbo.config.annotation.DubboService;
+
+@DubboService
+public class BenchmarkServiceImpl implements BenchmarkService {
+
+ @Override
+ public BenchmarkResponse unaryCall(BenchmarkRequest request) {
+ return new BenchmarkResponse(request.getPayload());
+ }
+}
diff --git
a/tools/benchmark/server/dubbo-java/src/main/resources/application.properties
b/tools/benchmark/server/dubbo-java/src/main/resources/application.properties
new file mode 100644
index 000000000..37f11013e
--- /dev/null
+++
b/tools/benchmark/server/dubbo-java/src/main/resources/application.properties
@@ -0,0 +1,7 @@
+server.port=20001
+
+dubbo.application.name=dubbo-java-benchmark
+dubbo.protocol.name=dubbo
+dubbo.protocol.port=20001
+dubbo.registry.address=N/A
+dubbo.scan.base-packages=org.apache.dubbo.benchmark
diff --git a/tools/benchmark/server/grpc/main.go
b/tools/benchmark/server/grpc/main.go
new file mode 100644
index 000000000..1dffac54c
--- /dev/null
+++ b/tools/benchmark/server/grpc/main.go
@@ -0,0 +1,101 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * 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 main
+
+import (
+ "context"
+ "flag"
+ "fmt"
+ "io"
+ "net"
+ "os"
+ "os/signal"
+ "syscall"
+)
+
+import (
+ "github.com/dubbogo/gost/log/logger"
+
+ "google.golang.org/grpc"
+)
+
+import (
+ benchmark "dubbo.apache.org/dubbo-go/v3/tools/benchmark/proto"
+)
+
+const separator = "========================================"
+
+var (
+ port = flag.Int("port", 50051, "Server port")
+)
+
+type benchmarkServiceImpl struct {
+ benchmark.UnimplementedBenchmarkServiceServer
+}
+
+func (s *benchmarkServiceImpl) UnaryCall(ctx context.Context, req
*benchmark.BenchmarkRequest) (*benchmark.BenchmarkResponse, error) {
+ return &benchmark.BenchmarkResponse{Payload: req.Payload}, nil
+}
+
+func (s *benchmarkServiceImpl) StreamCall(stream
benchmark.BenchmarkService_StreamCallServer) error {
+ for {
+ req, err := stream.Recv()
+ if err != nil {
+ if err == io.EOF {
+ return nil
+ }
+ return err
+ }
+ if err := stream.Send(&benchmark.BenchmarkResponse{Payload:
req.Payload}); err != nil {
+ return err
+ }
+ }
+}
+
+func main() {
+ flag.Parse()
+
+ logger.Info(separator)
+ logger.Info(" gRPC Benchmark Server")
+ logger.Info(separator)
+ logger.Infof("[INFO] Port: %d", *port)
+
+ lis, err := net.Listen("tcp", fmt.Sprintf(":%d", *port))
+ if err != nil {
+ logger.Fatalf("Failed to listen: %v", err)
+ }
+
+ s := grpc.NewServer()
+ benchmark.RegisterBenchmarkServiceServer(s, &benchmarkServiceImpl{})
+
+ go func() {
+ if err := s.Serve(lis); err != nil {
+ logger.Fatalf("Failed to start server: %v", err)
+ }
+ }()
+
+ logger.Infof("[INFO] Server started, listening on: 127.0.0.1:%d", *port)
+
+ sig := make(chan os.Signal, 1)
+ signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
+ <-sig
+
+ logger.Info("[INFO] Stopping server...")
+ s.GracefulStop()
+ logger.Info("[INFO] Server stopped")
+}