├── .github ├── CODEOWNERS └── workflows │ ├── codeql-analysis.yml │ ├── pr-check.yml │ ├── push-check.yml │ └── release-check.yml ├── .gitignore ├── .licenserc.yaml ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── CREDITS ├── README.md ├── docs └── images │ ├── client1_qps.png │ ├── client1_tp99.png │ ├── client1_tp999.png │ ├── client2_qps.png │ ├── client2_tp99.png │ ├── client2_tp999.png │ ├── client3_qps.png │ ├── client3_tp99.png │ ├── client3_tp999.png │ ├── server1_qps.png │ ├── server1_tp99.png │ ├── server1_tp999.png │ ├── server2_qps.png │ ├── server2_tp99.png │ ├── server2_tp999.png │ ├── server3_qps.png │ ├── server3_tp99.png │ └── server3_tp999.png ├── evio ├── codec │ └── rw.go ├── main.go └── rpc_server.go ├── gnet ├── codec │ └── rw.go ├── main.go ├── mux_server.go └── rpc_server.go ├── go.mod ├── go.sum ├── net ├── client │ ├── main.go │ ├── mux_client.go │ └── pool_client.go ├── codec │ └── rw.go ├── main.go ├── mux_server.go └── rpc_server.go ├── netpoll ├── client │ ├── main.go │ ├── mux_client.go │ └── pool_client.go ├── codec │ └── rw.go ├── main.go ├── mux_server.go └── rpc_server.go ├── runner ├── api.go ├── bench │ └── client.go ├── connpool │ ├── connpool.go │ ├── long_pool.go │ └── ring.go ├── counter.go ├── perf │ ├── cpu │ │ ├── calculator_linux.go │ │ ├── calculator_mock.go │ │ ├── cpu.go │ │ └── cpu_test.go │ ├── mem │ │ ├── mem.go │ │ ├── mem_linux.go │ │ ├── mem_mock.go │ │ └── mem_test.go │ └── recorder.go ├── processor.go ├── reporter.go ├── runner.go ├── svr │ └── server.go └── timer.go └── scripts ├── benchmark_client.sh ├── benchmark_server.sh ├── build.sh ├── env.sh ├── kill_servers.sh ├── reports └── render_images.py ├── run_servers.sh └── update_data.sh /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # For more information, please refer to https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners 2 | 3 | * @cloudwego/netpoll-reviewers @cloudwego/netpoll-approvers @cloudwego/netpoll-maintainers 4 | -------------------------------------------------------------------------------- /.github/workflows/codeql-analysis.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | name: "CodeQL" 13 | 14 | on: 15 | push: 16 | branches: [ develop, main ] 17 | pull_request: 18 | # The branches below must be a subset of the branches above 19 | branches: [ develop ] 20 | schedule: 21 | - cron: '35 21 * * 4' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze 26 | runs-on: ubuntu-latest 27 | permissions: 28 | actions: read 29 | contents: read 30 | security-events: write 31 | 32 | strategy: 33 | fail-fast: false 34 | matrix: 35 | language: [ 'go' ] 36 | # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] 37 | # Learn more: 38 | # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed 39 | 40 | steps: 41 | - name: Checkout repository 42 | uses: actions/checkout@v2 43 | 44 | - name: Set up Go 45 | uses: actions/setup-go@v2 46 | with: 47 | go-version: 1.16 48 | 49 | - uses: actions/cache@v2 50 | with: 51 | path: ~/go/pkg/mod 52 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 53 | restore-keys: | 54 | ${{ runner.os }}-go- 55 | 56 | # Initializes the CodeQL tools for scanning. 57 | - name: Initialize CodeQL 58 | uses: github/codeql-action/init@v1 59 | with: 60 | languages: ${{ matrix.language }} 61 | # If you wish to specify custom queries, you can do so here or in a config file. 62 | # By default, queries listed here will override any specified in a config file. 63 | # Prefix the list here with "+" to use these queries and those in the config file. 64 | # queries: ./path/to/local/query, your-org/your-repo/queries@main 65 | 66 | # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). 67 | # If this step fails, then you should remove it and run the build manually (see below) 68 | - name: Autobuild 69 | uses: github/codeql-action/autobuild@v1 70 | 71 | # ℹ️ Command-line programs to run using the OS shell. 72 | # 📚 https://git.io/JvXDl 73 | 74 | # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines 75 | # and modify them (or add more) to build your code if your project 76 | # uses a compiled language 77 | 78 | #- run: | 79 | # make bootstrap 80 | # make release 81 | 82 | - name: Perform CodeQL Analysis 83 | uses: github/codeql-action/analyze@v1 84 | -------------------------------------------------------------------------------- /.github/workflows/pr-check.yml: -------------------------------------------------------------------------------- 1 | name: Pull Request Check 2 | 3 | on: pull_request 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | 11 | - name: Set up Go 12 | uses: actions/setup-go@v2 13 | with: 14 | go-version: 1.16 15 | 16 | - uses: actions/cache@v2 17 | with: 18 | path: ~/go/pkg/mod 19 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 20 | restore-keys: | 21 | ${{ runner.os }}-go- 22 | 23 | - name: Benchmark 24 | run: go test -bench=. -benchmem -run=none ./... 25 | -------------------------------------------------------------------------------- /.github/workflows/push-check.yml: -------------------------------------------------------------------------------- 1 | name: Push Check 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v2 10 | 11 | - name: Set up Go 12 | uses: actions/setup-go@v2 13 | with: 14 | go-version: 1.16 15 | 16 | - uses: actions/cache@v2 17 | with: 18 | path: ~/go/pkg/mod 19 | key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }} 20 | restore-keys: | 21 | ${{ runner.os }}-go- 22 | 23 | - name: Check License Header 24 | uses: apache/skywalking-eyes@main 25 | env: 26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 27 | 28 | - name: Lint 29 | run: | 30 | test -z "$(gofmt -s -l .)" 31 | go vet -stdmethods=false $(go list ./...) 32 | 33 | - name: Unit Test 34 | run: go test -v -race -covermode=atomic -coverprofile=coverage.out ./... 35 | -------------------------------------------------------------------------------- /.github/workflows/release-check.yml: -------------------------------------------------------------------------------- 1 | name: Release Check 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v2 13 | 14 | - name: Check Source Branch 15 | run: python2 -c "exit(0 if '${{ github.head_ref }}'.startswith('release') or '${{ github.head_ref }}'.startswith('hotfix') else 1)" 16 | 17 | - name: Check Version 18 | run: | 19 | # get version code, runner not support grep -E here 20 | SOURCE_VERSION=`grep 'Version\s*=\s*\"v[0-9]\{1,\}\.[0-9]\{1,\}\.[0-9]\{1,\}\"' *.go | awk -F '\"' '{print $(NF-1)}'` 21 | git checkout main 22 | MASTER_VERSION=`grep 'Version\s*=\s*\"v[0-9]\{1,\}\.[0-9]\{1,\}\.[0-9]\{1,\}\"' *.go | awk -F '\"' '{print $(NF-1)}'` 23 | git checkout ${{Head.SHA}} 24 | # check version update 25 | python2 -c "exit(0 if list(map(int,'${SOURCE_VERSION#v}'.split('.')[:3])) > list(map(int,'${MASTER_VERSION#v}'.split('.')[:3])) else 1)" 26 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Binaries for programs and plugins 2 | *.exe 3 | *.exe~ 4 | *.dll 5 | *.so 6 | *.dylib 7 | 8 | # Test binary, built with `go test -c` 9 | *.test 10 | 11 | # Output of the go coverage tool, specifically when used with LiteIDE 12 | *.out 13 | 14 | # Dependency directories (remove the comment below to include it) 15 | # vendor/ 16 | 17 | .idea/ 18 | output/ 19 | *.log 20 | *.csv -------------------------------------------------------------------------------- /.licenserc.yaml: -------------------------------------------------------------------------------- 1 | header: 2 | license: 3 | spdx-id: Apache-2.0 4 | copyright-owner: CloudWeGo Authors 5 | 6 | paths: 7 | - '**/*.go' 8 | - '**/*.s' 9 | 10 | comment: on-failure -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | conduct@cloudwego.io. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | ## Your First Pull Request 4 | We use github for our codebase. You can start by reading [How To Pull Request](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/about-pull-requests). 5 | 6 | ## Without Semantic Versioning 7 | We keep the stable code in branch `main` like `golang.org/x`. Development base on branch `develop`. And we promise the **Forward Compatibility** by adding new package directory with suffix `v2/v3` when code has break changes. 8 | 9 | ## Branch Organization 10 | We use [git-flow](https://nvie.com/posts/a-successful-git-branching-model/) as our branch organization, as known as [FDD](https://en.wikipedia.org/wiki/Feature-driven_development) 11 | 12 | ## Bugs 13 | ### 1. How to Find Known Issues 14 | We are using [Github Issues](https://github.com/cloudwego/kitex/issues) for our public bugs. We keep a close eye on this and try to make it clear when we have an internal fix in progress. Before filing a new task, try to make sure your problem doesn’t already exist. 15 | 16 | ### 2. Reporting New Issues 17 | Providing a reduced test code is a recommended way for reporting issues. Then can placed in: 18 | - Just in issues 19 | - [Golang Playground](https://play.golang.org/) 20 | 21 | ### 3. Security Bugs 22 | Please do not report the safe disclosure of bugs to public issues. Contact us by [Support Email](mailto:conduct@cloudwego.io) 23 | 24 | ## How to Get in Touch 25 | - [Email](mailto:conduct@cloudwego.io) 26 | 27 | ## Submit a Pull Request 28 | Before you submit your Pull Request (PR) consider the following guidelines: 29 | 1. Search [GitHub](https://github.com/cloudwego/kitex/pulls) for an open or closed PR that relates to your submission. You don't want to duplicate existing efforts. 30 | 2. Be sure that an issue describes the problem you're fixing, or documents the design for the feature you'd like to add. Discussing the design upfront helps to ensure that we're ready to accept your work. 31 | 3. [Fork](https://docs.github.com/en/github/getting-started-with-github/fork-a-repo) the cloudwego/kitex repo. 32 | 4. In your forked repository, make your changes in a new git branch: 33 | ``` 34 | git checkout -b my-fix-branch develop 35 | ``` 36 | 5. Create your patch, including appropriate test cases. 37 | 6. Follow our [Style Guides](#code-style-guides). 38 | 7. Commit your changes using a descriptive commit message that follows [AngularJS Git Commit Message Conventions](https://docs.google.com/document/d/1QrDFcIiPjSLDn3EL15IJygNPiHORgU1_OOAqWjiDU5Y/edit). 39 | Adherence to these conventions is necessary because release notes are automatically generated from these messages. 40 | 8. Push your branch to GitHub: 41 | ``` 42 | git push origin my-fix-branch 43 | ``` 44 | 9. In GitHub, send a pull request to `kitex:develop` 45 | 46 | ## Contribution Prerequisites 47 | - Our development environment keeps up with [Go Official](https://golang.org/project/). 48 | - You need fully checking with lint tools before submit your pull request. [gofmt](https://golang.org/pkg/cmd/gofmt/) and [golangci-lint](https://github.com/golangci/golangci-lint) 49 | - You are familiar with [Github](https://github.com) 50 | - Maybe you need familiar with [Actions](https://github.com/features/actions)(our default workflow tool). 51 | 52 | ## Code Style Guides 53 | Also see [Pingcap General advice](https://pingcap.github.io/style-guide/general.html). 54 | 55 | Good resources: 56 | - [Effective Go](https://golang.org/doc/effective_go) 57 | - [Go Code Review Comments](https://github.com/golang/go/wiki/CodeReviewComments) 58 | - [Uber Go Style Guide](https://github.com/uber-go/guide/blob/master/style.md) 59 | -------------------------------------------------------------------------------- /CREDITS: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudwego/netpoll-benchmark/d97d53a5fbadce806707cfdd45e7f084c90ea583/CREDITS -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # netpoll-benchmark 2 | 3 | **置顶声明:** 4 | 5 | 本 [RPC 场景测试](#测试设定) 项目中,部分包含面向 [Redis 场景][redis] 的 [evio][evio], [gnet][gnet] 测试数据,不构成参考建议。 6 | 7 | ## 什么是 RPC 场景 8 | 9 | 这里所说的 [RPC 场景](#什么是-RPC-场景),是为了区别于 [Redis 场景][redis],特点是处理逻辑(Handler)耗时较长。 10 | 11 | 以 Server 为例,一次服务调用过程可描述为: 12 | 13 | *接收数据(Read Buffer) -> 解析请求(Decode) -> 逻辑处理(Handler) -> 响应编码(Encode) -> 发送数据(Write Buffer)* 14 | 15 | 如果上述过程处理速度很快(特别是 Handler), 则可以通过线程内串行处理的方式, 减少上下文切换,达到性能上的理论最优。 cache/proxy/纯计算类业务均属于这种情况,典型的如 [nginx][nginx] 16 | , [redis][redis] 等。 由于没有专门的术语描述,以下我们将其称为 [Redis 场景][redis]。 17 | 18 | 而 RPC 业务特征是 Handler 逻辑较重,耗时较长,显然是不能串行处理的。 因此 [RPC 场景](#什么是-RPC-场景),Handler 必须要异步处理,上下文切换、I/O 协作等都是必须考虑的代价。 19 | 20 | ## Netpoll 解决什么问题 21 | 22 | ### 1. 过多长连接下的问题: 23 | 24 | 线上微服务场景中,由于上下游实例众多,上下游之间的连接数是 k(M x N) 的关系,导致 client/server 均持有大量长连接。 而由于数据分发的负载均衡,长连接的工作总是间歇式的,即存在一定比例的空闲连接,以下描述为"空闲率"。 25 | 26 | 当我们使用 [go net][net] 编写 server 端代码时,必然的为每个连接都分配了一个独立的 goroutine,理想情况下,协程空闲率 = 连接空闲率; 而在 [netpoll][netpoll] 中,空闲连接是不持有 goroutine 的,只有正在工作的连接会在协程池中处理,这就尽可能的减少了协程数量,提高协程利用率。 27 | 28 | 我们构建了 [测试场景 2](#测试场景-2) 来评估空闲连接的代价。 29 | 30 | ### 2. 连接多路复用下的问题: 31 | 32 | 连接多路复用(以下简称 mux)是指在一条长连接上,合并「发送/接收」多个「请求/响应」。这种模式也是长连接利用率低下的一种解决方式。 33 | 34 | mux 性能瓶颈主要在 合并包/拆分包,这里涉及到「并行转队列」和「队列转并行」的具体实现。 35 | 36 | 常见的基于 [go net][net] 编写的框架,会采用 `go readloop(conn)` && `go writeloop(conn)` 方式,构建两个协程,`循环式(串行)的完成编解码`,这在 [go net][net] 下确实是最优的。 37 | 38 | 但是 [netpoll][netpoll] 有更优的实现,提供的 nocopy API 可以将编解码转为异步。为此,我们构建了 [测试场景 3](#测试场景-3) 来评估收益。 39 | 40 | #### PS: [netpoll][netpoll] 为什么不采用 `串行编解码` ? 41 | A: 这里需要明确指出 `串行编解码` 适用场景更有限,在 LT(水平触发) 下,`串行编解码` 意味着需要快速判断包大小。然而并不是所有 RPC 协议都有头部来表明长度,比如 HTTP、Thrift Buffered 等,这些协议下,转异步编解码是效率更好的方式。 42 | 43 | ## 测试设定 44 | 45 | 我们认可 [brpc][brpc] 的测试观点,上下文切换是 [RPC 场景](#什么是-RPC-场景) 必须付出的代价,每个线程独立循环地收发数据,对真实场景缺乏指导意义。 我们希望 benchmark 46 | 可以指导后续的优化工作,为了更切合 [RPC 场景](#什么是-RPC-场景) 测试,引入了以下设定: 47 | 48 | 1. 不能串行处理 49 | * 上述我们说明了 [RPC 场景](#什么是-RPC-场景) 和 [Redis 场景][redis]的区别,因此 Handler 必须异步执行。 50 | * `time.Sleep` 虽然能够规避串行处理,但 `timer` 唤醒拥有更高的调度优先级,干扰了 `runtime` 的正常调度,因此没有使用。 51 | 2. 不能边收边发 52 | * [RPC 场景](#什么是-RPC-场景),必须要接收到完整的数据包,才可以继续处理,不能接收到一半就开始回包。 53 | * [evio][evio], [gnet][gnet] 欠缺异步处理机制(异步关闭、资源清理等),因此我们做了近似处理,**不构成参考建议**。 54 | 3. 接收和发送的数据不能是同一份 55 | * [RPC 场景](#什么是-RPC-场景) 下的请求和响应,是通过序列化/反序列化产生的两份不同的数据。 56 | * 如果直接把读数据 buffer 直接写回,则忽略了这部分开销,并忽略了在这方面所做的一些优化。 57 | 58 | ## 开始测试 59 | 60 | `Echo 测试` 将收到的 string 数据原样返回,定义了 `Message` 来模拟编解码过程。 61 | 62 | 协议格式为 `header(4 Byte)(=payload length), payload(n Byte)`。 63 | 64 | [RPC 场景](#什么是-RPC-场景) 在 Client 和 Server 端使用方式不同,因此分别测试两端的性能表现。 65 | 66 | * Client 测试:不同的 client,调用相同的 net server,评估 client 端性能表现。 67 | * Server 测试:相同的 net client,调用不同的 server,评估 server 端性能表现 68 | 69 | ### 测试场景 1 70 | 71 | `测试场景 1` 模拟「长连接池模式」下的 `Echo 测试`,用来评估连接池场景的性能上限。 72 | 73 | * 连接池容量 = 1024 74 | 75 | ### 测试场景 2 76 | 77 | `测试场景 2` 模拟「带空闲连接」下的 `Echo 测试`,用来评估空闲连接对性能的影响。 78 | 79 | * 连接空闲率 = 80% 80 | 81 | ### 测试场景 3 82 | 83 | `测试场景 3` 模拟「连接多路复用模式」下的 `Echo 测试`,用来评估拆包/组包的实际效率。 84 | 85 | * 连接个数 = 4 86 | 87 | ### 快速执行:loopback 模式 88 | 89 | 以上测试场景,通过执行「脚本 + 场景号」,可以快速开始测试。执行前请先确认满足[环境要求](#环境要求)。 90 | 91 | * client 测试 92 | 93 | ```bash 94 | ./scripts/benchmark_client.sh 1 # or 2, 3 95 | ``` 96 | 97 | * server 测试 98 | 99 | ```bash 100 | ./scripts/benchmark_server.sh 1 # or 2, 3 101 | ``` 102 | 103 | ### 环境要求 104 | 105 | OS: Linux 106 | 107 | * 默认依赖了命令 `taskset`, 限定 client 和 server 运行的 CPU; 如在其他系统执行, 请修改脚本。 108 | 109 | CPU: 推荐配置 >=20核, 最低要求 >=4核 110 | 111 | * 压测脚本默认需要 20核 CPU, 具体在脚本的 `taskset -c ...` 部分, 可以修改或删除。 112 | 113 | 软件依赖: Python3 + matplotlib 用于绘图 114 | 115 | * 在运行压测前, 可以使用 pip3 install matplotlib 安装依赖 116 | 117 | 118 | ## 参考数据 (echo size 1KB) 119 | 120 | 相关说明: 121 | 122 | 该压测数据是在调用端有充分机器资源**压满服务端**的情况下测试,更侧重于关注服务端性能。后续会提供调用端性能数据情况。 123 | 124 | ### 配置 125 | 126 | * CPU: Intel(R) Xeon(R) CPU E5-2630 v4 @ 2.20GHz 127 | * 运行限定 server 4CPU, client 16CPU 128 | * OS: Debian 6.4.7-amd64 x86_64 GNU/Linux 129 | * Go: go1.21.4 130 | 131 | ### [测试场景 1](#测试场景-1) 132 | 133 | | | QPS | TP99 | TP999 | 134 | | :---------- | :------------------------------------ | :------------------------------------: | :-------------------------------------: | 135 | | Server(4C) | ![image](docs/images/server1_qps.png) | ![image](docs/images/server1_tp99.png) | ![image](docs/images/server1_tp999.png) | 136 | | Client(16C) | ![image](docs/images/client1_qps.png) | ![image](docs/images/client1_tp99.png) | ![image](docs/images/client1_tp999.png) | 137 | 138 | ### [测试场景 2](#测试场景-2) 139 | 140 | | | QPS | TP99 | TP999 | 141 | | :---------- | :------------------------------------ | :------------------------------------: | :-------------------------------------: | 142 | | Server(4C) | ![image](docs/images/server2_qps.png) | ![image](docs/images/server2_tp99.png) | ![image](docs/images/server2_tp999.png) | 143 | | Client(16C) | ![image](docs/images/client2_qps.png) | ![image](docs/images/client2_tp99.png) | ![image](docs/images/client2_tp999.png) | 144 | 145 | 146 | ### [测试场景 3](#测试场景-3) 147 | 148 | | | QPS | TP99 | TP999 | 149 | | :---------- | :------------------------------------ | :------------------------------------: | :-------------------------------------: | 150 | | Server(4C) | ![image](docs/images/server3_qps.png) | ![image](docs/images/server3_tp99.png) | ![image](docs/images/server3_tp999.png) | 151 | | Client(16C) | ![image](docs/images/client3_qps.png) | ![image](docs/images/client3_tp99.png) | ![image](docs/images/client3_tp999.png) | 152 | 153 | 154 | [net]: https://github.com/golang/go/tree/master/src/net 155 | 156 | [netpoll]: https://github.com/cloudwego/netpoll 157 | 158 | [gnet]: https://github.com/panjf2000/gnet 159 | 160 | [evio]: https://github.com/tidwall/evio 161 | 162 | [redis]: https://redis.io 163 | 164 | [nginx]: https://www.nginx.com 165 | 166 | [brpc]: https://github.com/apache/incubator-brpc/blob/master/docs/cn/benchmark.md 167 | 168 | -------------------------------------------------------------------------------- /docs/images/client1_qps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudwego/netpoll-benchmark/d97d53a5fbadce806707cfdd45e7f084c90ea583/docs/images/client1_qps.png -------------------------------------------------------------------------------- /docs/images/client1_tp99.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudwego/netpoll-benchmark/d97d53a5fbadce806707cfdd45e7f084c90ea583/docs/images/client1_tp99.png -------------------------------------------------------------------------------- /docs/images/client1_tp999.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudwego/netpoll-benchmark/d97d53a5fbadce806707cfdd45e7f084c90ea583/docs/images/client1_tp999.png -------------------------------------------------------------------------------- /docs/images/client2_qps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudwego/netpoll-benchmark/d97d53a5fbadce806707cfdd45e7f084c90ea583/docs/images/client2_qps.png -------------------------------------------------------------------------------- /docs/images/client2_tp99.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudwego/netpoll-benchmark/d97d53a5fbadce806707cfdd45e7f084c90ea583/docs/images/client2_tp99.png -------------------------------------------------------------------------------- /docs/images/client2_tp999.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudwego/netpoll-benchmark/d97d53a5fbadce806707cfdd45e7f084c90ea583/docs/images/client2_tp999.png -------------------------------------------------------------------------------- /docs/images/client3_qps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudwego/netpoll-benchmark/d97d53a5fbadce806707cfdd45e7f084c90ea583/docs/images/client3_qps.png -------------------------------------------------------------------------------- /docs/images/client3_tp99.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudwego/netpoll-benchmark/d97d53a5fbadce806707cfdd45e7f084c90ea583/docs/images/client3_tp99.png -------------------------------------------------------------------------------- /docs/images/client3_tp999.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudwego/netpoll-benchmark/d97d53a5fbadce806707cfdd45e7f084c90ea583/docs/images/client3_tp999.png -------------------------------------------------------------------------------- /docs/images/server1_qps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudwego/netpoll-benchmark/d97d53a5fbadce806707cfdd45e7f084c90ea583/docs/images/server1_qps.png -------------------------------------------------------------------------------- /docs/images/server1_tp99.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudwego/netpoll-benchmark/d97d53a5fbadce806707cfdd45e7f084c90ea583/docs/images/server1_tp99.png -------------------------------------------------------------------------------- /docs/images/server1_tp999.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudwego/netpoll-benchmark/d97d53a5fbadce806707cfdd45e7f084c90ea583/docs/images/server1_tp999.png -------------------------------------------------------------------------------- /docs/images/server2_qps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudwego/netpoll-benchmark/d97d53a5fbadce806707cfdd45e7f084c90ea583/docs/images/server2_qps.png -------------------------------------------------------------------------------- /docs/images/server2_tp99.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudwego/netpoll-benchmark/d97d53a5fbadce806707cfdd45e7f084c90ea583/docs/images/server2_tp99.png -------------------------------------------------------------------------------- /docs/images/server2_tp999.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudwego/netpoll-benchmark/d97d53a5fbadce806707cfdd45e7f084c90ea583/docs/images/server2_tp999.png -------------------------------------------------------------------------------- /docs/images/server3_qps.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudwego/netpoll-benchmark/d97d53a5fbadce806707cfdd45e7f084c90ea583/docs/images/server3_qps.png -------------------------------------------------------------------------------- /docs/images/server3_tp99.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudwego/netpoll-benchmark/d97d53a5fbadce806707cfdd45e7f084c90ea583/docs/images/server3_tp99.png -------------------------------------------------------------------------------- /docs/images/server3_tp999.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cloudwego/netpoll-benchmark/d97d53a5fbadce806707cfdd45e7f084c90ea583/docs/images/server3_tp999.png -------------------------------------------------------------------------------- /evio/codec/rw.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package codec 18 | 19 | import ( 20 | "encoding/binary" 21 | "sync" 22 | 23 | "github.com/tidwall/evio" 24 | 25 | "github.com/cloudwego/netpoll-benchmark/runner" 26 | ) 27 | 28 | var connermap sync.Map 29 | 30 | type Conner struct { 31 | input []byte 32 | inlen int 33 | } 34 | 35 | func GetOrNewConner(conn evio.Conn) *Conner { 36 | c, _ := connermap.LoadOrStore(conn, &Conner{}) 37 | conner := c.(*Conner) 38 | return conner 39 | } 40 | 41 | // encode 42 | func (c *Conner) Encode(msg *runner.Message) (out []byte) { 43 | out = make([]byte, 4+len(msg.Message)) 44 | binary.BigEndian.PutUint32(out[:4], uint32(len(msg.Message))) 45 | copy(out[4:], msg.Message) 46 | return out 47 | } 48 | 49 | // decode 50 | func (c *Conner) Decode(in []byte) (msg *runner.Message, err error) { 51 | c.input = append(c.input, in...) 52 | l := len(c.input) 53 | if c.inlen <= 0 { 54 | if l < 4 { 55 | return nil, nil 56 | } 57 | c.inlen = int(binary.BigEndian.Uint32(c.input[:4])) + 4 58 | } 59 | if l < c.inlen { 60 | return nil, nil 61 | } 62 | msg = &runner.Message{} 63 | msg.Message = string(c.input[4:c.inlen]) 64 | // reset 65 | c.input = append(c.input[:0], c.input[c.inlen:]...) 66 | c.inlen = 0 67 | return msg, nil 68 | } 69 | -------------------------------------------------------------------------------- /evio/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "github.com/cloudwego/netpoll-benchmark/runner" 21 | "github.com/cloudwego/netpoll-benchmark/runner/perf" 22 | "github.com/cloudwego/netpoll-benchmark/runner/svr" 23 | ) 24 | 25 | func main() { 26 | svr.Serve(NewServer) 27 | } 28 | 29 | var reporter = perf.NewRecorder("EVIO@Server") 30 | 31 | func NewServer(mode runner.Mode) runner.Server { 32 | switch mode { 33 | case runner.Mode_Echo, runner.Mode_Idle: 34 | return NewRPCServer() 35 | case runner.Mode_Mux: 36 | // no implement 37 | } 38 | return nil 39 | } 40 | -------------------------------------------------------------------------------- /evio/rpc_server.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "fmt" 21 | "runtime" 22 | 23 | "github.com/tidwall/evio" 24 | 25 | "github.com/cloudwego/netpoll-benchmark/evio/codec" 26 | "github.com/cloudwego/netpoll-benchmark/runner" 27 | ) 28 | 29 | func NewRPCServer() runner.Server { 30 | return &rpcServer{} 31 | } 32 | 33 | var _ runner.Server = &rpcServer{} 34 | 35 | type rpcServer struct{} 36 | 37 | func (s *rpcServer) Run(network, address string) error { 38 | events := &evio.Events{ 39 | NumLoops: runtime.GOMAXPROCS(0), 40 | LoadBalance: evio.RoundRobin, 41 | Data: rpcHandler, 42 | } 43 | return evio.Serve(*events, fmt.Sprintf(`%s://%s`, network, address)) 44 | } 45 | 46 | // evio 无法实现设定的测试场景 47 | func rpcHandler(c evio.Conn, in []byte) (out []byte, action evio.Action) { 48 | conner := codec.GetOrNewConner(c) 49 | req, err := conner.Decode(in) 50 | if err != nil { 51 | return nil, evio.Close 52 | } 53 | if req == nil { 54 | return nil, evio.None 55 | } 56 | 57 | // 禁止串行处理 58 | var ch = make(chan int) 59 | go func() { 60 | // handler 61 | resp := runner.ProcessRequest(reporter, req) 62 | 63 | // encode 64 | out = conner.Encode(resp) 65 | action = evio.None 66 | ch <- 1 67 | }() 68 | <-ch 69 | return out, action 70 | } 71 | -------------------------------------------------------------------------------- /gnet/codec/rw.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package codec 18 | 19 | import ( 20 | "encoding/binary" 21 | "sync" 22 | 23 | "github.com/panjf2000/gnet" 24 | 25 | "github.com/cloudwego/netpoll-benchmark/runner" 26 | ) 27 | 28 | var connermap sync.Map 29 | 30 | type Conner struct { 31 | input []byte 32 | inlen int 33 | } 34 | 35 | func GetOrNewConner(conn gnet.Conn) *Conner { 36 | c, _ := connermap.LoadOrStore(conn, &Conner{}) 37 | conner := c.(*Conner) 38 | return conner 39 | } 40 | 41 | // encode 42 | func (c *Conner) Encode(msg *runner.Message) (out []byte) { 43 | out = make([]byte, 4+len(msg.Message)) 44 | binary.BigEndian.PutUint32(out[:4], uint32(len(msg.Message))) 45 | copy(out[4:], msg.Message) 46 | return out 47 | } 48 | 49 | // decode 50 | func (c *Conner) Decode(in []byte) (msg *runner.Message, err error) { 51 | c.input = append(c.input, in...) 52 | l := len(c.input) 53 | if c.inlen <= 0 { 54 | if l < 4 { 55 | return nil, nil 56 | } 57 | c.inlen = int(binary.BigEndian.Uint32(c.input[:4])) + 4 58 | } 59 | if l < c.inlen { 60 | return nil, nil 61 | } 62 | msg = &runner.Message{} 63 | msg.Message = string(c.input[4:c.inlen]) 64 | // reset 65 | c.input = append(c.input[:0], c.input[c.inlen:]...) 66 | c.inlen = 0 67 | return msg, nil 68 | } 69 | -------------------------------------------------------------------------------- /gnet/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "github.com/cloudwego/netpoll-benchmark/runner" 21 | "github.com/cloudwego/netpoll-benchmark/runner/perf" 22 | "github.com/cloudwego/netpoll-benchmark/runner/svr" 23 | ) 24 | 25 | func main() { 26 | svr.Serve(NewServer) 27 | } 28 | 29 | var reporter = perf.NewRecorder("GNET@Server") 30 | 31 | func NewServer(mode runner.Mode) runner.Server { 32 | switch mode { 33 | case runner.Mode_Echo, runner.Mode_Idle: 34 | return NewRPCServer() 35 | case runner.Mode_Mux: 36 | return NewMuxServer() 37 | } 38 | return nil 39 | } 40 | -------------------------------------------------------------------------------- /gnet/mux_server.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "fmt" 21 | "sync" 22 | 23 | "github.com/panjf2000/gnet" 24 | "github.com/panjf2000/gnet/pkg/pool/goroutine" 25 | 26 | "github.com/cloudwego/netpoll-benchmark/gnet/codec" 27 | "github.com/cloudwego/netpoll-benchmark/runner" 28 | ) 29 | 30 | func NewMuxServer() runner.Server { 31 | return &muxServer{} 32 | } 33 | 34 | var _ runner.Server = &muxServer{} 35 | 36 | type muxServer struct { 37 | *gnet.EventServer 38 | pool *goroutine.Pool 39 | } 40 | 41 | func (s *muxServer) Run(network, address string) error { 42 | p := goroutine.Default() 43 | defer p.Release() 44 | 45 | echo := &muxServer{pool: p} 46 | return gnet.Serve(echo, fmt.Sprintf("%s://%s", network, address), gnet.WithMulticore(true)) 47 | } 48 | 49 | func (s *muxServer) React(frame []byte, c gnet.Conn) (out []byte, action gnet.Action) { 50 | mux := getOrNewMuxConn(c) 51 | for { 52 | req, err := mux.conner.Decode(frame) 53 | if err != nil { 54 | return nil, gnet.Close 55 | } 56 | if req == nil { 57 | return nil, gnet.None 58 | } 59 | frame = []byte{} 60 | 61 | // Use ants pool to unblock the event-loop. 62 | s.pool.Submit(func() { 63 | // handler 64 | resp := runner.ProcessRequest(reporter, req) 65 | // encode 66 | mux.wch <- resp 67 | }) 68 | } 69 | } 70 | 71 | var muxconnmap sync.Map 72 | 73 | func getOrNewMuxConn(conn gnet.Conn) *muxConn { 74 | c, loaded := muxconnmap.LoadOrStore(conn, &muxConn{}) 75 | mux := c.(*muxConn) 76 | if loaded { 77 | return mux 78 | } 79 | // init 80 | mux.conn = conn 81 | mux.conner = codec.GetOrNewConner(conn) 82 | mux.wch = make(chan *runner.Message) 83 | go mux.loopWrite() 84 | return mux 85 | } 86 | 87 | type muxConn struct { 88 | conn gnet.Conn 89 | conner *codec.Conner 90 | wch chan *runner.Message 91 | } 92 | 93 | func (mux *muxConn) loopWrite() { 94 | for { 95 | msg := <-mux.wch 96 | // encode 97 | out := mux.conner.Encode(msg) 98 | err := mux.conn.AsyncWrite(out) 99 | if err != nil { 100 | panic(fmt.Errorf("mux encode failed: %s", err.Error())) 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /gnet/rpc_server.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "fmt" 21 | 22 | "github.com/panjf2000/gnet" 23 | "github.com/panjf2000/gnet/pkg/pool/goroutine" 24 | 25 | "github.com/cloudwego/netpoll-benchmark/gnet/codec" 26 | "github.com/cloudwego/netpoll-benchmark/runner" 27 | ) 28 | 29 | func NewRPCServer() runner.Server { 30 | return &rpcServer{} 31 | } 32 | 33 | var _ runner.Server = &rpcServer{} 34 | 35 | type rpcServer struct { 36 | *gnet.EventServer 37 | pool *goroutine.Pool 38 | } 39 | 40 | func (s *rpcServer) Run(network, address string) error { 41 | p := goroutine.Default() 42 | defer p.Release() 43 | 44 | echo := &rpcServer{pool: p} 45 | return gnet.Serve(echo, fmt.Sprintf("%s://%s", network, address), gnet.WithMulticore(true)) 46 | } 47 | 48 | func (s *rpcServer) React(frame []byte, c gnet.Conn) (out []byte, action gnet.Action) { 49 | conner := codec.GetOrNewConner(c) 50 | req, err := conner.Decode(frame) 51 | if err != nil { 52 | return nil, gnet.Close 53 | } 54 | if req == nil { 55 | return nil, gnet.None 56 | } 57 | 58 | // Use ants pool to unblock the event-loop. 59 | s.pool.Submit(func() { 60 | // handler 61 | resp := runner.ProcessRequest(reporter, req) 62 | 63 | // encode 64 | out = conner.Encode(resp) 65 | err := c.AsyncWrite(out) 66 | if err != nil { 67 | panic(err) 68 | } 69 | }) 70 | return 71 | } 72 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/cloudwego/netpoll-benchmark 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/cloudwego/netpoll v0.5.1 7 | github.com/montanaflynn/stats v0.7.1 8 | github.com/panjf2000/gnet v1.6.7 9 | github.com/tidwall/evio v1.0.8 10 | ) 11 | 12 | require ( 13 | github.com/bytedance/gopkg v0.0.0-20230728082804-614d0af6619b // indirect 14 | github.com/kavu/go_reuseport v1.5.0 // indirect 15 | github.com/panjf2000/ants/v2 v2.4.7 // indirect 16 | github.com/valyala/bytebufferpool v1.0.0 // indirect 17 | go.uber.org/multierr v1.11.0 // indirect 18 | go.uber.org/zap v1.26.0 // indirect 19 | golang.org/x/sys v0.14.0 // indirect 20 | gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect 21 | ) 22 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= 3 | github.com/bytedance/gopkg v0.0.0-20220413063733-65bf48ffb3a7/go.mod h1:2ZlV9BaUH4+NXIBF0aMdKKAnHTzqH+iMU4KUjAbL23Q= 4 | github.com/bytedance/gopkg v0.0.0-20230728082804-614d0af6619b h1:R6PWoQtxEMpWJPHnpci+9LgFxCS7iJCfOGBvCgZeTKI= 5 | github.com/bytedance/gopkg v0.0.0-20230728082804-614d0af6619b/go.mod h1:FtQG3YbQG9L/91pbKSw787yBQPutC+457AvDW77fgUQ= 6 | github.com/cloudwego/netpoll v0.5.1 h1:zDUF7xF0C97I10fGlQFJ4jg65khZZMUvSu/TWX44Ohc= 7 | github.com/cloudwego/netpoll v0.5.1/go.mod h1:xVefXptcyheopwNDZjDPcfU6kIjZXZ4nY550k1yH9eQ= 8 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 10 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 11 | github.com/kavu/go_reuseport v1.5.0 h1:UNuiY2OblcqAtVDE8Gsg1kZz8zbBWg907sP1ceBV+bk= 12 | github.com/kavu/go_reuseport v1.5.0/go.mod h1:CG8Ee7ceMFSMnx/xr25Vm0qXaj2Z4i5PWoUx+JZ5/CU= 13 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 14 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 15 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 16 | github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= 17 | github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= 18 | github.com/panjf2000/ants/v2 v2.4.7 h1:MZnw2JRyTJxFwtaMtUJcwE618wKD04POWk2gwwP4E2M= 19 | github.com/panjf2000/ants/v2 v2.4.7/go.mod h1:f6F0NZVFsGCp5A7QW/Zj/m92atWwOkY0OIhFxRNFr4A= 20 | github.com/panjf2000/gnet v1.6.7 h1:zv1k6kw80sG5ZQrLpbbFDheNCm50zm3z2e3ck5GwMOM= 21 | github.com/panjf2000/gnet v1.6.7/go.mod h1:KcOU7QsCaCBjeD5kyshBIamG3d9kAQtlob4Y0v0E+sc= 22 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 23 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 24 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 25 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 26 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 27 | github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= 28 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 29 | github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= 30 | github.com/tidwall/evio v1.0.8 h1:+M7lh83rL4KwEObDGtXP3J1wE5utH80LeaAhrKCGVfE= 31 | github.com/tidwall/evio v1.0.8/go.mod h1:MJhRp4iVVqx/n/5mJk77oKmSABVhC7yYykcJiKaFYYw= 32 | github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= 33 | github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= 34 | github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= 35 | go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 36 | go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= 37 | go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= 38 | go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= 39 | go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= 40 | go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= 41 | go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= 42 | go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= 43 | go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= 44 | go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo= 45 | go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so= 46 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 47 | golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 48 | golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= 49 | golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= 50 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 51 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 52 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 53 | golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= 54 | golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= 55 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 56 | golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 57 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 58 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 59 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 60 | golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 61 | golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 62 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 63 | golang.org/x/sys v0.0.0-20211204120058-94396e421777/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 64 | golang.org/x/sys v0.0.0-20220110181412-a018aaa089fe/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 65 | golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 66 | golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= 67 | golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= 68 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 69 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 70 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 71 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 72 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 73 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 74 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= 75 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 76 | golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= 77 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 78 | golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 79 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 80 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 81 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 82 | gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= 83 | gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= 84 | gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= 85 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 86 | gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 87 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 88 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 89 | gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 90 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 91 | -------------------------------------------------------------------------------- /net/client/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "github.com/cloudwego/netpoll-benchmark/runner" 21 | "github.com/cloudwego/netpoll-benchmark/runner/bench" 22 | ) 23 | 24 | // main is use for routing. 25 | func main() { 26 | bench.Benching(NewClient) 27 | } 28 | 29 | func NewClient(mode runner.Mode, network, address string) runner.Client { 30 | switch mode { 31 | case runner.Mode_Echo, runner.Mode_Idle: 32 | return NewClientWithConnpool(network, address) 33 | case runner.Mode_Mux: 34 | return NewClientWithMux(network, address, 4) 35 | } 36 | return nil 37 | } 38 | -------------------------------------------------------------------------------- /net/client/mux_client.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "fmt" 21 | "net" 22 | "sync/atomic" 23 | "time" 24 | 25 | "github.com/cloudwego/netpoll-benchmark/net/codec" 26 | "github.com/cloudwego/netpoll-benchmark/runner" 27 | ) 28 | 29 | func NewClientWithMux(network, address string, size int) runner.Client { 30 | cli := &muxclient{} 31 | cli.network = network 32 | cli.address = address 33 | cli.size = uint64(size) 34 | cli.conns = make([]*muxConn, size) 35 | for i := range cli.conns { 36 | cn, err := cli.DialTimeout(network, address, time.Second) 37 | if err != nil { 38 | panic(fmt.Errorf("mux dial conn failed: %s", err.Error())) 39 | } 40 | mc := newMuxConn(cn.(net.Conn)) 41 | cli.conns[i] = mc 42 | } 43 | return cli 44 | } 45 | 46 | var _ runner.Client = &muxclient{} 47 | 48 | type muxclient struct { 49 | network string 50 | address string 51 | conns []*muxConn 52 | size uint64 53 | cursor uint64 54 | } 55 | 56 | func (cli *muxclient) DialTimeout(network, address string, timeout time.Duration) (runner.Conn, error) { 57 | return net.DialTimeout(network, address, timeout) 58 | } 59 | 60 | func (cli *muxclient) Echo(req *runner.Message) (resp *runner.Message, err error) { 61 | // get conn & codec 62 | conn := cli.conns[atomic.AddUint64(&cli.cursor, 1)%cli.size] 63 | conn.wch <- req 64 | resp = <-conn.rch 65 | 66 | // reporter 67 | runner.ProcessResponse(resp) 68 | return resp, nil 69 | } 70 | 71 | func newMuxConn(conn net.Conn) *muxConn { 72 | mc := &muxConn{} 73 | mc.conn = conn 74 | mc.conner = codec.NewConner(conn) 75 | mc.rch = make(chan *runner.Message) 76 | mc.wch = make(chan *runner.Message) 77 | go mc.loopRead() 78 | go mc.loopWrite() 79 | return mc 80 | } 81 | 82 | type muxConn struct { 83 | conn net.Conn 84 | conner *codec.Conner 85 | rch, wch chan *runner.Message 86 | } 87 | 88 | func (mux *muxConn) loopRead() { 89 | for { 90 | msg := &runner.Message{} 91 | err := mux.conner.Decode(msg) 92 | if err != nil { 93 | panic(fmt.Errorf("mux decode failed: %s", err.Error())) 94 | } 95 | mux.rch <- msg 96 | } 97 | } 98 | 99 | func (mux *muxConn) loopWrite() { 100 | for { 101 | msg := <-mux.wch 102 | err := mux.conner.Encode(msg) 103 | if err != nil { 104 | panic(fmt.Errorf("mux encode failed: %s", err.Error())) 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /net/client/pool_client.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "net" 21 | "time" 22 | 23 | "github.com/cloudwego/netpoll-benchmark/net/codec" 24 | "github.com/cloudwego/netpoll-benchmark/runner" 25 | "github.com/cloudwego/netpoll-benchmark/runner/connpool" 26 | ) 27 | 28 | func NewClientWithConnpool(network, address string) runner.Client { 29 | return &poolclient{ 30 | network: network, 31 | address: address, 32 | connpool: connpool.NewLongPool(1024), 33 | } 34 | } 35 | 36 | var _ runner.Client = &poolclient{} 37 | 38 | type poolclient struct { 39 | network string 40 | address string 41 | connpool connpool.ConnPool 42 | } 43 | 44 | func (cli *poolclient) DialTimeout(network, address string, timeout time.Duration) (runner.Conn, error) { 45 | return net.DialTimeout(network, address, timeout) 46 | } 47 | 48 | func (cli *poolclient) Echo(req *runner.Message) (resp *runner.Message, err error) { 49 | // get conn & codec 50 | cn, err := cli.connpool.Get(cli.network, cli.address, cli, time.Second) 51 | if err != nil { 52 | return nil, err 53 | } 54 | defer func() { 55 | cli.connpool.Put(cn, err) 56 | }() 57 | conner := codec.NewConner(cn.(net.Conn)) 58 | defer codec.PutConner(conner) 59 | 60 | // encode 61 | err = conner.Encode(req) 62 | if err != nil { 63 | return nil, err 64 | } 65 | 66 | // decode 67 | resp = &runner.Message{} 68 | err = conner.Decode(resp) 69 | if err != nil { 70 | return nil, err 71 | } 72 | 73 | // reporter 74 | runner.ProcessResponse(resp) 75 | return resp, nil 76 | } 77 | -------------------------------------------------------------------------------- /net/codec/rw.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package codec 18 | 19 | import ( 20 | "bufio" 21 | "encoding/binary" 22 | "io" 23 | "net" 24 | "sync" 25 | "unsafe" 26 | 27 | "github.com/cloudwego/netpoll-benchmark/runner" 28 | ) 29 | 30 | const pagesize = 8192 31 | 32 | var connerpool sync.Pool 33 | 34 | type Conner struct { 35 | header []byte 36 | // conn net.Conn 37 | rw *bufio.ReadWriter 38 | } 39 | 40 | func NewConner(conn net.Conn) *Conner { 41 | size := pagesize 42 | if v := connerpool.Get(); v != nil { 43 | conner := v.(*Conner) 44 | // conner.conn = conn 45 | conner.rw.Reader.Reset(conn) 46 | conner.rw.Writer.Reset(conn) 47 | return conner 48 | } 49 | conner := &Conner{} 50 | conner.header = make([]byte, 4) 51 | // conner.conn = conn 52 | conner.rw = bufio.NewReadWriter(bufio.NewReaderSize(conn, size), bufio.NewWriterSize(conn, size)) 53 | return conner 54 | } 55 | 56 | func PutConner(conner *Conner) { 57 | conner.rw.Reader.Reset(nil) 58 | conner.rw.Writer.Reset(nil) 59 | connerpool.Put(conner) 60 | } 61 | 62 | // encode 63 | func (c *Conner) Encode(msg *runner.Message) (err error) { 64 | binary.BigEndian.PutUint32(c.header, uint32(len(msg.Message))) 65 | _, err = c.rw.Write(c.header) 66 | if err == nil { 67 | _, err = c.rw.WriteString(msg.Message) 68 | } 69 | if err == nil { 70 | err = c.rw.Flush() 71 | } 72 | return err 73 | } 74 | 75 | // decode 76 | func (c *Conner) Decode(msg *runner.Message) (err error) { 77 | // decode 78 | bLen, err := c.rw.Peek(4) 79 | if err == nil { 80 | _, err = c.rw.Discard(4) 81 | } 82 | if err != nil { 83 | return err 84 | } 85 | 86 | l := binary.BigEndian.Uint32(bLen) 87 | payload := make([]byte, l) 88 | _, err = io.ReadFull(c.rw, payload) 89 | if err != nil { 90 | return err 91 | } 92 | msg.Message = unsafeSliceToString(payload) 93 | return nil 94 | } 95 | 96 | // zero-copy slice convert to string 97 | func unsafeSliceToString(b []byte) string { 98 | return *(*string)(unsafe.Pointer(&b)) 99 | } 100 | -------------------------------------------------------------------------------- /net/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "github.com/cloudwego/netpoll-benchmark/runner" 21 | "github.com/cloudwego/netpoll-benchmark/runner/perf" 22 | "github.com/cloudwego/netpoll-benchmark/runner/svr" 23 | ) 24 | 25 | func main() { 26 | svr.Serve(NewServer) 27 | } 28 | 29 | var reporter = perf.NewRecorder("NET@Server") 30 | 31 | func NewServer(mode runner.Mode) runner.Server { 32 | switch mode { 33 | case runner.Mode_Echo, runner.Mode_Idle: 34 | return NewRPCServer() 35 | case runner.Mode_Mux: 36 | return NewMuxServer() 37 | } 38 | return nil 39 | } 40 | -------------------------------------------------------------------------------- /net/mux_server.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "fmt" 21 | "io" 22 | "net" 23 | "strings" 24 | "time" 25 | 26 | "github.com/cloudwego/netpoll-benchmark/net/codec" 27 | "github.com/cloudwego/netpoll-benchmark/runner" 28 | ) 29 | 30 | func NewMuxServer() runner.Server { 31 | return &muxServer{} 32 | } 33 | 34 | var _ runner.Server = &muxServer{} 35 | 36 | type muxServer struct{} 37 | 38 | func (s *muxServer) Run(network, address string) error { 39 | // new listener 40 | listener, _ := net.Listen(network, address) 41 | var conns = make([]*muxConn, 0, 1024) 42 | for { 43 | _conn, err := listener.Accept() 44 | if err != nil { 45 | if strings.Contains(err.Error(), "closed") { 46 | return err 47 | } 48 | time.Sleep(10 * time.Millisecond) // too many open files ? 49 | continue 50 | } 51 | 52 | mc := newMuxConn(_conn) 53 | conns = append(conns, mc) 54 | } 55 | } 56 | 57 | func newMuxConn(conn net.Conn) *muxConn { 58 | mc := &muxConn{} 59 | mc.conn = conn 60 | mc.conner = codec.NewConner(conn) 61 | mc.wch = make(chan *runner.Message) 62 | mc.ech = make(chan string) 63 | go mc.loopRead() 64 | go mc.loopWrite() 65 | return mc 66 | } 67 | 68 | type muxConn struct { 69 | conn net.Conn 70 | conner *codec.Conner 71 | wch chan *runner.Message 72 | ech chan string 73 | } 74 | 75 | func (mux *muxConn) loopRead() { 76 | for { 77 | msg := &runner.Message{} 78 | err := mux.conner.Decode(msg) 79 | if err != nil { 80 | if err == io.EOF { 81 | mux.ech <- "EOF" 82 | // connection is closed. 83 | break 84 | } 85 | panic(fmt.Errorf("mux decode failed: %s", err.Error())) 86 | } 87 | 88 | // handler must use another goroutine 89 | go func() { 90 | // handler 91 | resp := runner.ProcessRequest(reporter, msg) 92 | // encode 93 | mux.wch <- resp 94 | }() 95 | } 96 | } 97 | 98 | func (mux *muxConn) loopWrite() { 99 | for { 100 | select { 101 | case msg := <-mux.wch: 102 | err := mux.conner.Encode(msg) 103 | if err != nil { 104 | panic(fmt.Errorf("mux encode failed: %s", err.Error())) 105 | } 106 | case <-mux.ech: 107 | return 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /net/rpc_server.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "net" 21 | "strings" 22 | "time" 23 | 24 | "github.com/cloudwego/netpoll-benchmark/net/codec" 25 | "github.com/cloudwego/netpoll-benchmark/runner" 26 | ) 27 | 28 | func NewRPCServer() runner.Server { 29 | return &rpcServer{} 30 | } 31 | 32 | var _ runner.Server = &rpcServer{} 33 | 34 | type rpcServer struct{} 35 | 36 | func (s *rpcServer) Run(network, address string) error { 37 | // new listener 38 | listener, _ := net.Listen(network, address) 39 | for { 40 | _conn, err := listener.Accept() 41 | if err != nil { 42 | if strings.Contains(err.Error(), "closed") { 43 | return err 44 | } 45 | time.Sleep(10 * time.Millisecond) // too many open files ? 46 | continue 47 | } 48 | 49 | go func(conn net.Conn) { 50 | conner := codec.NewConner(conn) 51 | defer codec.PutConner(conner) 52 | 53 | var err error 54 | for err == nil { 55 | err = s.handler(conner) 56 | } 57 | conn.Close() 58 | }(_conn) 59 | } 60 | } 61 | 62 | func (s *rpcServer) handler(conner *codec.Conner) (err error) { 63 | // decode 64 | req := &runner.Message{} 65 | err = conner.Decode(req) 66 | if err != nil { 67 | return err 68 | } 69 | 70 | // handler 71 | resp := runner.ProcessRequest(reporter, req) 72 | 73 | // encode 74 | err = conner.Encode(resp) 75 | if err != nil { 76 | return err 77 | } 78 | return nil 79 | } 80 | -------------------------------------------------------------------------------- /netpoll/client/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "runtime" 21 | 22 | "github.com/cloudwego/netpoll" 23 | 24 | "github.com/cloudwego/netpoll-benchmark/runner" 25 | "github.com/cloudwego/netpoll-benchmark/runner/bench" 26 | ) 27 | 28 | // main is use for routing. 29 | func main() { 30 | bench.Benching(NewClient) 31 | } 32 | 33 | func init() { 34 | netpoll.DisableGopool() 35 | netpoll.SetNumLoops(runtime.GOMAXPROCS(0)) 36 | } 37 | 38 | func NewClient(mode runner.Mode, network, address string) runner.Client { 39 | switch mode { 40 | case runner.Mode_Echo, runner.Mode_Idle: 41 | return NewClientWithConnpool(network, address) 42 | case runner.Mode_Mux: 43 | return NewClientWithMux(network, address, 4) 44 | } 45 | return nil 46 | } 47 | -------------------------------------------------------------------------------- /netpoll/client/mux_client.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "context" 21 | "encoding/binary" 22 | "fmt" 23 | "sync/atomic" 24 | "time" 25 | 26 | "github.com/cloudwego/netpoll" 27 | "github.com/cloudwego/netpoll/mux" 28 | 29 | "github.com/cloudwego/netpoll-benchmark/netpoll/codec" 30 | "github.com/cloudwego/netpoll-benchmark/runner" 31 | ) 32 | 33 | func NewClientWithMux(network, address string, size int) runner.Client { 34 | cli := &muxclient{} 35 | cli.network = network 36 | cli.address = address 37 | cli.size = uint64(size) 38 | cli.conns = make([]*muxConn, size) 39 | for i := range cli.conns { 40 | cn, err := cli.DialTimeout(network, address, time.Second) 41 | if err != nil { 42 | panic(fmt.Errorf("mux dial conn failed: %s", err.Error())) 43 | } 44 | mc := newMuxConn(cn.(netpoll.Connection)) 45 | cli.conns[i] = mc 46 | } 47 | return cli 48 | } 49 | 50 | var _ runner.Client = &muxclient{} 51 | 52 | type muxclient struct { 53 | network string 54 | address string 55 | conns []*muxConn 56 | size uint64 57 | cursor uint64 58 | } 59 | 60 | func (cli *muxclient) DialTimeout(network, address string, timeout time.Duration) (runner.Conn, error) { 61 | return netpoll.DialConnection(network, address, timeout) 62 | } 63 | 64 | func (cli *muxclient) Echo(req *runner.Message) (resp *runner.Message, err error) { 65 | // get conn & codec 66 | mc := cli.conns[atomic.AddUint64(&cli.cursor, 1)%cli.size] 67 | 68 | // encode 69 | writer := netpoll.NewLinkBuffer() 70 | err = codec.Encode(writer, req) 71 | if err != nil { 72 | return nil, err 73 | } 74 | mc.Put(func() (buf netpoll.Writer, isNil bool) { 75 | return writer, false 76 | }) 77 | 78 | // decode 79 | reader := <-mc.rch 80 | resp = &runner.Message{} 81 | err = codec.Decode(reader, resp) 82 | if err != nil { 83 | return nil, err 84 | } 85 | 86 | // reporter 87 | runner.ProcessResponse(resp) 88 | return resp, nil 89 | } 90 | 91 | func newMuxConn(conn netpoll.Connection) *muxConn { 92 | mc := &muxConn{} 93 | mc.conn = conn 94 | mc.rch = make(chan netpoll.Reader) 95 | // loop read 96 | conn.SetOnRequest(func(ctx context.Context, connection netpoll.Connection) error { 97 | reader := connection.Reader() 98 | // decode 99 | bLen, err := reader.Peek(4) 100 | if err != nil { 101 | return err 102 | } 103 | l := int(binary.BigEndian.Uint32(bLen)) 104 | r, _ := reader.Slice(l + 4) 105 | mc.rch <- r 106 | return nil 107 | }) 108 | // loop write 109 | mc.wqueue = mux.NewShardQueue(mux.ShardSize, conn) 110 | return mc 111 | } 112 | 113 | type muxConn struct { 114 | conn netpoll.Connection 115 | rch chan netpoll.Reader 116 | wqueue *mux.ShardQueue // use for write 117 | 118 | } 119 | 120 | // Put puts the buffer getter back to the queue. 121 | func (c *muxConn) Put(gt mux.WriterGetter) { 122 | c.wqueue.Add(gt) 123 | } 124 | -------------------------------------------------------------------------------- /netpoll/client/pool_client.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "time" 21 | 22 | "github.com/cloudwego/netpoll" 23 | 24 | "github.com/cloudwego/netpoll-benchmark/netpoll/codec" 25 | "github.com/cloudwego/netpoll-benchmark/runner" 26 | "github.com/cloudwego/netpoll-benchmark/runner/connpool" 27 | ) 28 | 29 | func NewClientWithConnpool(network, address string) runner.Client { 30 | return &poolclient{ 31 | network: network, 32 | address: address, 33 | connpool: connpool.NewLongPool(1024), 34 | } 35 | } 36 | 37 | var _ runner.Client = &poolclient{} 38 | 39 | type poolclient struct { 40 | network string 41 | address string 42 | connpool connpool.ConnPool 43 | } 44 | 45 | func (cli *poolclient) DialTimeout(network, address string, timeout time.Duration) (runner.Conn, error) { 46 | return netpoll.DialConnection(network, address, timeout) 47 | } 48 | 49 | func (cli *poolclient) Echo(req *runner.Message) (resp *runner.Message, err error) { 50 | // get conn & codec 51 | cn, err := cli.connpool.Get(cli.network, cli.address, cli, time.Second) 52 | if err != nil { 53 | return nil, err 54 | } 55 | defer func() { 56 | cli.connpool.Put(cn, err) 57 | }() 58 | conn := cn.(netpoll.Connection) 59 | reader, writer := conn.Reader(), conn.Writer() 60 | 61 | // encode 62 | err = codec.Encode(writer, req) 63 | if err != nil { 64 | return nil, err 65 | } 66 | 67 | // decode 68 | resp = &runner.Message{} 69 | err = codec.Decode(reader, resp) 70 | if err != nil { 71 | return nil, err 72 | } 73 | 74 | // reporter 75 | runner.ProcessResponse(resp) 76 | return resp, nil 77 | } 78 | -------------------------------------------------------------------------------- /netpoll/codec/rw.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package codec 18 | 19 | import ( 20 | "encoding/binary" 21 | 22 | "github.com/cloudwego/netpoll" 23 | 24 | "github.com/cloudwego/netpoll-benchmark/runner" 25 | ) 26 | 27 | // encode 28 | func Encode(writer netpoll.Writer, msg *runner.Message) (err error) { 29 | header, _ := writer.Malloc(4) 30 | binary.BigEndian.PutUint32(header, uint32(len(msg.Message))) 31 | 32 | writer.WriteString(msg.Message) 33 | err = writer.Flush() 34 | return err 35 | } 36 | 37 | // decode 38 | func Decode(reader netpoll.Reader, msg *runner.Message) (err error) { 39 | bLen, err := reader.Next(4) 40 | if err != nil { 41 | return err 42 | } 43 | l := int(binary.BigEndian.Uint32(bLen)) 44 | 45 | msg.Message, err = reader.ReadString(l) 46 | if err != nil { 47 | return err 48 | } 49 | err = reader.Release() 50 | return err 51 | } 52 | -------------------------------------------------------------------------------- /netpoll/main.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "github.com/cloudwego/netpoll" 21 | 22 | "github.com/cloudwego/netpoll-benchmark/runner" 23 | "github.com/cloudwego/netpoll-benchmark/runner/perf" 24 | "github.com/cloudwego/netpoll-benchmark/runner/svr" 25 | ) 26 | 27 | func main() { 28 | svr.Serve(NewServer) 29 | } 30 | 31 | var reporter = perf.NewRecorder("NETPOLL@Server") 32 | 33 | func init() { 34 | netpoll.DisableGopool() 35 | } 36 | 37 | func NewServer(mode runner.Mode) runner.Server { 38 | switch mode { 39 | case runner.Mode_Echo, runner.Mode_Idle: 40 | return NewRPCServer() 41 | case runner.Mode_Mux: 42 | return NewMuxServer() 43 | } 44 | return nil 45 | } 46 | -------------------------------------------------------------------------------- /netpoll/mux_server.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "context" 21 | "encoding/binary" 22 | "fmt" 23 | 24 | "github.com/cloudwego/netpoll" 25 | "github.com/cloudwego/netpoll/mux" 26 | 27 | "github.com/cloudwego/netpoll-benchmark/netpoll/codec" 28 | "github.com/cloudwego/netpoll-benchmark/runner" 29 | ) 30 | 31 | func NewMuxServer() runner.Server { 32 | return &muxServer{} 33 | } 34 | 35 | var _ runner.Server = &muxServer{} 36 | 37 | type muxServer struct{} 38 | 39 | func (s *muxServer) Run(network, address string) error { 40 | // new listener 41 | listener, err := netpoll.CreateListener(network, address) 42 | if err != nil { 43 | panic(err) 44 | } 45 | 46 | // new server 47 | opt := netpoll.WithOnPrepare(s.prepare) 48 | eventLoop, err := netpoll.NewEventLoop(s.handler, opt) 49 | if err != nil { 50 | panic(err) 51 | } 52 | // start listen loop ... 53 | return eventLoop.Serve(listener) 54 | } 55 | 56 | type connkey struct{} 57 | 58 | var ctxkey connkey 59 | 60 | func (s *muxServer) prepare(conn netpoll.Connection) context.Context { 61 | mc := newMuxConn(conn) 62 | ctx := context.WithValue(context.Background(), ctxkey, mc) 63 | return ctx 64 | } 65 | 66 | func (s *muxServer) handler(ctx context.Context, conn netpoll.Connection) (err error) { 67 | mc := ctx.Value(ctxkey).(*muxConn) 68 | reader := conn.Reader() 69 | 70 | bLen, err := reader.Peek(4) 71 | if err != nil { 72 | return err 73 | } 74 | length := int(binary.BigEndian.Uint32(bLen)) + 4 75 | 76 | r2, err := reader.Slice(length) 77 | if err != nil { 78 | return err 79 | } 80 | 81 | // handler must use another goroutine 82 | go func() { 83 | req := &runner.Message{} 84 | err = codec.Decode(r2, req) 85 | if err != nil { 86 | panic(fmt.Errorf("netpoll decode failed: %s", err.Error())) 87 | } 88 | 89 | // handler 90 | resp := runner.ProcessRequest(reporter, req) 91 | 92 | // encode 93 | writer := netpoll.NewLinkBuffer() 94 | err = codec.Encode(writer, resp) 95 | if err != nil { 96 | panic(fmt.Errorf("netpoll encode failed: %s", err.Error())) 97 | } 98 | mc.Put(func() (buf netpoll.Writer, isNil bool) { 99 | return writer, false 100 | }) 101 | }() 102 | return nil 103 | } 104 | 105 | func newMuxConn(conn netpoll.Connection) *muxConn { 106 | mc := &muxConn{} 107 | mc.conn = conn 108 | mc.wqueue = mux.NewShardQueue(mux.ShardSize, conn) 109 | return mc 110 | } 111 | 112 | type muxConn struct { 113 | conn netpoll.Connection 114 | wqueue *mux.ShardQueue // use for write 115 | } 116 | 117 | // Put puts the buffer getter back to the queue. 118 | func (c *muxConn) Put(gt mux.WriterGetter) { 119 | c.wqueue.Add(gt) 120 | } 121 | -------------------------------------------------------------------------------- /netpoll/rpc_server.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "context" 21 | 22 | "github.com/cloudwego/netpoll" 23 | 24 | "github.com/cloudwego/netpoll-benchmark/netpoll/codec" 25 | "github.com/cloudwego/netpoll-benchmark/runner" 26 | ) 27 | 28 | func NewRPCServer() runner.Server { 29 | return &rpcServer{} 30 | } 31 | 32 | var _ runner.Server = &rpcServer{} 33 | 34 | type rpcServer struct{} 35 | 36 | func (s *rpcServer) Run(network, address string) error { 37 | // new listener 38 | listener, err := netpoll.CreateListener(network, address) 39 | if err != nil { 40 | panic(err) 41 | } 42 | 43 | // new server 44 | eventLoop, err := netpoll.NewEventLoop(s.handler) 45 | if err != nil { 46 | panic(err) 47 | } 48 | // start listen loop ... 49 | return eventLoop.Serve(listener) 50 | } 51 | 52 | func (s *rpcServer) handler(ctx context.Context, conn netpoll.Connection) (err error) { 53 | reader, writer := conn.Reader(), conn.Writer() 54 | 55 | // decode 56 | req := &runner.Message{} 57 | err = codec.Decode(reader, req) 58 | if err != nil { 59 | return err 60 | } 61 | 62 | // handler 63 | resp := runner.ProcessRequest(reporter, req) 64 | 65 | // encode 66 | err = codec.Encode(writer, resp) 67 | if err != nil { 68 | return err 69 | } 70 | return nil 71 | } 72 | -------------------------------------------------------------------------------- /runner/api.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package runner 18 | 19 | import ( 20 | "time" 21 | ) 22 | 23 | /* Payload Format 24 | * Header: 4 bytes(indicate payload length) 25 | * Body: length bytes 26 | */ 27 | type Message struct { 28 | Message string 29 | } 30 | 31 | type Client interface { 32 | DialTimeout(network, address string, timeout time.Duration) (conn Conn, err error) 33 | Echo(req *Message) (resp *Message, err error) 34 | } 35 | 36 | type ClientNewer func(mode Mode, network, address string) Client 37 | 38 | type Server interface { 39 | Run(network, address string) error 40 | } 41 | 42 | type ServerNewer func(mode Mode) Server 43 | 44 | // 屏蔽各 conn 定义的差异 45 | type Conn interface { 46 | Close() error 47 | } 48 | 49 | type EchoOnce func(req *Message) (resp *Message, err error) 50 | 51 | /* 52 | * Transport Mode: 53 | * 1. simple cho (connpool) 54 | * 2. long connpool + idle conns 55 | * 3. conn multiplexing 56 | */ 57 | type Mode int 58 | 59 | const ( 60 | Mode_Echo Mode = 1 61 | Mode_Idle Mode = 2 // 50% idle conn 62 | Mode_Mux Mode = 3 63 | ) 64 | -------------------------------------------------------------------------------- /runner/bench/client.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package bench 18 | 19 | import ( 20 | "flag" 21 | "fmt" 22 | "time" 23 | 24 | "github.com/cloudwego/netpoll-benchmark/runner" 25 | ) 26 | 27 | func Benching(newer runner.ClientNewer) { 28 | initFlags() 29 | // prepare 30 | mod := runner.Mode(mode) 31 | client := newer(mod, network, address) 32 | var once runner.EchoOnce = client.Echo 33 | var conns = make([]runner.Conn, 4*concurrent) 34 | switch mod { 35 | case runner.Mode_Idle: 36 | // add idle conn 80% 37 | for i := range conns { 38 | conn, err := client.DialTimeout(network, address, dialTimeout) 39 | if err != nil { 40 | panic(fmt.Errorf("dial idle conn failed: %s", err.Error())) 41 | } 42 | conns[i] = conn 43 | } 44 | } 45 | // benching 46 | r := runner.NewRunner() 47 | r.Run(name, once, concurrent, total, echoSize) 48 | } 49 | 50 | var ( 51 | name string 52 | address string 53 | echoSize int 54 | total int64 55 | concurrent int 56 | mode int 57 | 58 | network string = "tcp" 59 | dialTimeout time.Duration = 50 * time.Millisecond 60 | ) 61 | 62 | func initFlags() { 63 | flag.StringVar(&name, "name", "", "which repo to bench") 64 | flag.StringVar(&address, "addr", "", "client call address") 65 | flag.IntVar(&echoSize, "b", 1024, "echo size once") 66 | flag.IntVar(&concurrent, "c", 1, "call concurrent") 67 | flag.Int64Var(&total, "n", 1, "call total nums") 68 | flag.IntVar(&mode, "mode", 1, "1: echo, 2: idle, 3: mux") 69 | flag.Parse() 70 | } 71 | -------------------------------------------------------------------------------- /runner/connpool/connpool.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package connpool 18 | 19 | import ( 20 | "time" 21 | 22 | "github.com/cloudwego/netpoll-benchmark/runner" 23 | ) 24 | 25 | // Dialer is used to dial and get a connection. 26 | type Dialer interface { 27 | DialTimeout(network, address string, timeout time.Duration) (runner.Conn, error) 28 | } 29 | 30 | // ConnPool is used to get connections. 31 | type ConnPool interface { 32 | // Get returns a connection to the given address. 33 | Get(network, address string, dialer Dialer, dialTimeout time.Duration) (runner.Conn, error) 34 | 35 | // Put puts the connection back to pool. 36 | // Note that the Close method of conn may already be invoked. 37 | Put(conn runner.Conn, err error) error 38 | 39 | // Close is to release resource of ConnPool, it is executed when client is closed. 40 | Close() error 41 | } 42 | 43 | // IsActive is used to check if the connection is active. 44 | type IsActive interface { 45 | IsActive() bool 46 | } 47 | -------------------------------------------------------------------------------- /runner/connpool/long_pool.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package connpool 18 | 19 | import ( 20 | "time" 21 | 22 | "github.com/cloudwego/netpoll-benchmark/runner" 23 | ) 24 | 25 | func NewLongPool(maxIdle int) ConnPool { 26 | return &longpool{ 27 | ring: NewRing(maxIdle), 28 | } 29 | } 30 | 31 | var _ ConnPool = &longpool{} 32 | 33 | // Peer has one address, it manage all connections base on this address 34 | type longpool struct { 35 | ring *Ring 36 | } 37 | 38 | // Get picks up connection from ring or dial a new one. 39 | func (p *longpool) Get(network, address string, dialer Dialer, dialTimeout time.Duration) (runner.Conn, error) { 40 | for { 41 | conn, _ := p.ring.Pop().(runner.Conn) 42 | if conn == nil { 43 | break 44 | } 45 | return conn, nil 46 | } 47 | conn, err := dialer.DialTimeout(network, address, dialTimeout) 48 | if err != nil { 49 | return nil, err 50 | } 51 | return conn, nil 52 | } 53 | 54 | func (p *longpool) Put(conn runner.Conn, err error) error { 55 | if err != nil { 56 | return conn.Close() 57 | } 58 | err = p.ring.Push(conn) 59 | if err != nil { 60 | return conn.Close() 61 | } 62 | return nil 63 | } 64 | 65 | // Close closes the peer and all the connections in the ring. 66 | func (p *longpool) Close() error { 67 | for { 68 | conn, _ := p.ring.Pop().(runner.Conn) 69 | if conn == nil { 70 | break 71 | } 72 | conn.Close() 73 | } 74 | return nil 75 | } 76 | -------------------------------------------------------------------------------- /runner/connpool/ring.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package connpool 18 | 19 | import ( 20 | "errors" 21 | "sync" 22 | ) 23 | 24 | // ErrRingFull means the ring is full. 25 | var ErrRingFull = errors.New("ring is full") 26 | 27 | // Ring implements a fixed size ring buffer to manage data 28 | type Ring struct { 29 | l sync.Mutex 30 | arr []interface{} 31 | size int 32 | tail int 33 | head int 34 | } 35 | 36 | // NewRing creates a ringbuffer with fixed size. 37 | func NewRing(size int) *Ring { 38 | if size <= 0 { 39 | // When size is an invalid number, we still return an instance 40 | // with zero-size to reduce error checks of the callers. 41 | size = 0 42 | } 43 | return &Ring{ 44 | arr: make([]interface{}, size+1), 45 | size: size, 46 | } 47 | } 48 | 49 | // Push appends item to the ring. 50 | func (r *Ring) Push(i interface{}) error { 51 | r.l.Lock() 52 | defer r.l.Unlock() 53 | if r.isFull() { 54 | return ErrRingFull 55 | } 56 | r.arr[r.head] = i 57 | r.head = r.inc() 58 | return nil 59 | } 60 | 61 | // Pop returns the last item and removes it from the ring. 62 | func (r *Ring) Pop() interface{} { 63 | r.l.Lock() 64 | defer r.l.Unlock() 65 | if r.isEmpty() { 66 | return nil 67 | } 68 | c := r.arr[r.tail] 69 | r.arr[r.tail] = nil 70 | r.tail = r.dec() 71 | return c 72 | } 73 | 74 | func (r *Ring) inc() int { 75 | return (r.head + 1) % (r.size + 1) 76 | } 77 | 78 | func (r *Ring) dec() int { 79 | return (r.tail + 1) % (r.size + 1) 80 | } 81 | 82 | func (r *Ring) isEmpty() bool { 83 | return r.tail == r.head 84 | } 85 | 86 | func (r *Ring) isFull() bool { 87 | return r.inc() == r.tail 88 | } 89 | -------------------------------------------------------------------------------- /runner/counter.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package runner 18 | 19 | import ( 20 | "sync/atomic" 21 | ) 22 | 23 | // 计数器 24 | type Counter struct { 25 | Total int64 // 总调用次数(limiter) 26 | Failed int64 // 失败次数 27 | costs []int64 // 耗时统计 28 | } 29 | 30 | func NewCounter() *Counter { 31 | return &Counter{} 32 | } 33 | 34 | func (c *Counter) Reset(total int64) { 35 | atomic.StoreInt64(&c.Total, 0) 36 | atomic.StoreInt64(&c.Failed, 0) 37 | c.costs = make([]int64, total) 38 | } 39 | 40 | func (c *Counter) AddRecord(idx int64, err error, cost int64) { 41 | c.costs[idx] = cost 42 | if err != nil { 43 | atomic.AddInt64(&c.Failed, 1) 44 | } 45 | } 46 | 47 | // idx < 0 表示用尽 48 | func (c *Counter) Idx() (idx int64) { 49 | return atomic.AddInt64(&c.Total, 1) - 1 50 | } 51 | -------------------------------------------------------------------------------- /runner/perf/cpu/calculator_linux.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package cpu 18 | 19 | /* 20 | #include 21 | */ 22 | import "C" 23 | import ( 24 | "context" 25 | "fmt" 26 | "io/ioutil" 27 | "log" 28 | "os" 29 | "strconv" 30 | "strings" 31 | "time" 32 | ) 33 | 34 | /** 35 | * man 5 proc 36 | */ 37 | func getPidCPUUsage(ctx context.Context, pid int) chan float64 { 38 | result := make(chan float64) 39 | 40 | go func() { 41 | ticker := time.NewTicker(defaultInterval) 42 | defer func() { 43 | ticker.Stop() 44 | close(result) 45 | }() 46 | clockTick := float64(C.sysconf(C._SC_CLK_TCK)) // ticks per sec, 机器时钟每秒所走的时钟打点数 // 100.0 // 47 | lastTime := time.Now() 48 | lastPIDTime, err := getPIDTime(pid) 49 | if err != nil { 50 | log.Fatalf("calculate cpu failed: err=%v", err) 51 | } 52 | finished := false 53 | for !finished { 54 | select { 55 | case <-ticker.C: 56 | case <-ctx.Done(): 57 | finished = true 58 | } 59 | 60 | nowTime := time.Now() 61 | pidTime, err := getPIDTime(pid) 62 | if err != nil { 63 | log.Fatalf("calculate cpu failed: err=%v", err) 64 | } 65 | 66 | diffPIDTime := pidTime - lastPIDTime 67 | period := float64(nowTime.Sub(lastTime).Milliseconds()) / 1000 //seconds in float 68 | pidCPUUsage := (diffPIDTime) * 100 / (clockTick * period) 69 | lastPIDTime = pidTime 70 | lastTime = nowTime 71 | 72 | result <- pidCPUUsage 73 | } 74 | }() 75 | 76 | return result 77 | } 78 | 79 | // getPIDTime get the sum of utime,stime,cutime,cstime 80 | func getPIDTime(pid int) (float64, error) { 81 | pidCPUStat := fmt.Sprintf("/proc/%d/stat", pid) 82 | cpuRet, err := readFile(pidCPUStat) 83 | if err != nil { 84 | return 0, err 85 | } 86 | items := strings.Split(cpuRet, " ") 87 | 88 | var pidTime float64 89 | if len(items) < 20 { 90 | return 0, fmt.Errorf("get cpu info of pid=%d invalid, result=%v", pid, items) 91 | } 92 | // 13 is utime: 93 | // Amount of time that this process has been scheduled in user mode, measured in clock ticks (divide by sysconf(_SC_CLK_TCK)). 94 | // This includes guest time, guest_time (time spent running a virtual CPU, see below), 95 | // so that applications that are not aware of the guest time field do not lose that time from their calculations. 96 | // 14 is stime: 97 | // Amount of time that this process has been scheduled in kernel mode, measured in clock ticks (divide by sysconf(_SC_CLK_TCK)). 98 | // 15 is cutime: 99 | // Amount of time that this process's waited-for children have been scheduled in user mode, measured in clock ticks (divide by sysconf(_SC_CLK_TCK)). (See also times(2).) 100 | // This includes guest time, cguest_time (time spent running a virtual CPU, see below). 101 | // 16 is cstime: 102 | // Amount of time that this process's waited-for children have been scheduled in kernel mode, measured in clock ticks (divide by sysconf(_SC_CLK_TCK)). 103 | for i := 13; i <= 16; i++ { 104 | if t, err := strconv.ParseFloat(items[i], 64); err != nil { 105 | return 0, err 106 | } else { 107 | pidTime += t 108 | } 109 | } 110 | return pidTime, nil 111 | } 112 | 113 | func readFile(fileName string) (string, error) { 114 | fd, err := os.Open(fileName) 115 | if err != nil { 116 | return "", err 117 | } 118 | defer fd.Close() 119 | b, err := ioutil.ReadAll(fd) 120 | content := string(b) 121 | return content, nil 122 | } 123 | -------------------------------------------------------------------------------- /runner/perf/cpu/calculator_mock.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build darwin || netbsd || freebsd || openbsd || dragonfly 16 | // +build darwin netbsd freebsd openbsd dragonfly 17 | 18 | package cpu 19 | 20 | import ( 21 | "context" 22 | "math/rand" 23 | "time" 24 | ) 25 | 26 | func getPidCPUUsage(ctx context.Context, pid int) chan float64 { 27 | result := make(chan float64, 1) 28 | go func() { 29 | ticker := time.NewTicker(defaultInterval) 30 | defer func() { 31 | ticker.Stop() 32 | close(result) 33 | }() 34 | for { 35 | select { 36 | case <-ticker.C: 37 | case <-ctx.Done(): 38 | return 39 | } 40 | 41 | result <- rand.Float64() * 100 42 | } 43 | }() 44 | 45 | return result 46 | } 47 | -------------------------------------------------------------------------------- /runner/perf/cpu/cpu.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package cpu 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "os" 23 | "sort" 24 | "time" 25 | ) 26 | 27 | const ( 28 | defaultInterval = time.Second * 1 29 | defaultUsageThreshold = 10 // % 30 | ) 31 | 32 | type Usage struct { 33 | Min float64 34 | Max float64 35 | Avg float64 36 | P50 float64 37 | P90 float64 38 | P99 float64 39 | } 40 | 41 | func (u Usage) String() string { 42 | return fmt.Sprintf( 43 | "MIN: %0.2f%%, TP50: %0.2f%%, TP90: %0.2f%%, TP99: %0.2f%%, MAX: %0.2f%%, AVG:%0.2f%%", 44 | u.Min, u.P50, u.P90, u.P99, u.Max, u.Avg, 45 | ) 46 | } 47 | 48 | func RecordUsage(ctx context.Context) (Usage, error) { 49 | pid := os.Getpid() 50 | return RecordPidUsage(ctx, pid) 51 | } 52 | 53 | func RecordPidUsage(ctx context.Context, pid int) (Usage, error) { 54 | if err := isPIDExist(pid); err != nil { 55 | return Usage{}, err 56 | } 57 | var cpuUsageList []float64 58 | for percent := range getPidCPUUsage(ctx, pid) { 59 | if percent > defaultUsageThreshold { 60 | cpuUsageList = append(cpuUsageList, percent) 61 | } 62 | } 63 | return statistic(cpuUsageList), nil 64 | } 65 | 66 | func statistic(stats []float64) Usage { 67 | if len(stats) == 0 { 68 | return Usage{} 69 | } 70 | 71 | sort.Float64s(stats) 72 | length := len(stats) 73 | if length > 3 { 74 | stats = stats[1 : length-1] 75 | length = length - 2 76 | } 77 | fLen := float64(len(stats)) 78 | tp50Index := int(fLen * 0.5) 79 | tp90Index := int(fLen * 0.9) 80 | tp99Index := int(fLen * 0.99) 81 | 82 | var usage Usage 83 | if tp50Index > 0 { 84 | usage.P50 = stats[tp50Index-1] 85 | } 86 | if tp90Index > tp50Index { 87 | usage.P90 = stats[tp90Index-1] 88 | } else { 89 | usage.P90 = usage.P50 90 | } 91 | if tp99Index > tp90Index { 92 | usage.P99 = stats[tp99Index-1] 93 | } else { 94 | usage.P99 = usage.P90 95 | } 96 | 97 | var sum float64 98 | for _, cost := range stats { 99 | sum += cost 100 | } 101 | usage.Avg = sum / fLen 102 | 103 | usage.Min = stats[0] 104 | usage.Max = stats[length-1] 105 | 106 | return usage 107 | } 108 | 109 | func isPIDExist(pid int) error { 110 | _, err := os.FindProcess(pid) 111 | return err 112 | } 113 | -------------------------------------------------------------------------------- /runner/perf/cpu/cpu_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package cpu 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "testing" 23 | "time" 24 | ) 25 | 26 | func TestRecordUsage(t *testing.T) { 27 | ctx, finished := context.WithCancel(context.Background()) 28 | go func() { 29 | defer finished() 30 | sum := 0 31 | beginAt := time.Now() 32 | for { 33 | if time.Now().Sub(beginAt) > defaultInterval*5 { 34 | return 35 | } 36 | sum += 1 37 | } 38 | }() 39 | usage, err := RecordUsage(ctx) 40 | if err != nil { 41 | t.Fatal(err) 42 | } 43 | fmt.Println(usage.String()) 44 | } 45 | -------------------------------------------------------------------------------- /runner/perf/mem/mem.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package mem 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "os" 23 | "regexp" 24 | "strconv" 25 | "strings" 26 | "time" 27 | ) 28 | 29 | const ( 30 | defaultInterval = time.Second * 3 31 | defaultUsageThreshold = 1024 // KB 32 | ) 33 | 34 | type Stats struct { 35 | Rss int64 36 | Pss int64 37 | } 38 | 39 | type Usage struct { 40 | MaxRss int64 41 | AvgRss int64 42 | } 43 | 44 | func (u Usage) String() string { 45 | return fmt.Sprintf("AVG: %d KB, MAX: %d KB", u.AvgRss, u.MaxRss) 46 | } 47 | 48 | func RecordUsage(ctx context.Context) (usage Usage, err error) { 49 | pid := os.Getpid() 50 | return RecordPidUsage(ctx, pid) 51 | } 52 | 53 | func RecordPidUsage(ctx context.Context, pid int) (usage Usage, err error) { 54 | ticker := time.NewTicker(defaultInterval) 55 | defer ticker.Stop() 56 | if pid < 0 { 57 | pid = os.Getpid() 58 | } 59 | var ( 60 | stats Stats 61 | bucket []Stats 62 | ) 63 | for { 64 | stats, err = getStats(pid) 65 | if err != nil { 66 | return 67 | } 68 | if stats.Rss > defaultUsageThreshold { 69 | bucket = append(bucket, stats) 70 | } 71 | 72 | select { 73 | case <-ctx.Done(): 74 | return calcStats(bucket), nil 75 | case <-ticker.C: 76 | } 77 | } 78 | } 79 | 80 | func calcStats(bucket []Stats) Usage { 81 | var ( 82 | totalRss int64 83 | maxRss int64 84 | ) 85 | for _, s := range bucket { 86 | totalRss += s.Rss 87 | if s.Rss > maxRss { 88 | maxRss = s.Rss 89 | } 90 | } 91 | return Usage{ 92 | MaxRss: maxRss, 93 | AvgRss: totalRss / int64(len(bucket)), 94 | } 95 | } 96 | 97 | func parseSmaps(data string) Stats { 98 | lines := strings.Split(data, "\n") 99 | reg := regexp.MustCompile("(\\w+): +(\\d+) kB") 100 | stats := Stats{} 101 | for _, line := range lines { 102 | params := reg.FindStringSubmatch(line) 103 | if len(params) < 3 { 104 | continue 105 | } 106 | field, value := params[1], params[2] 107 | var err error 108 | switch strings.ToLower(field) { 109 | case "rss": 110 | stats.Rss, err = strconv.ParseInt(value, 10, 64) 111 | case "pss": 112 | stats.Pss, err = strconv.ParseInt(value, 10, 64) 113 | } 114 | if err != nil { 115 | panic(fmt.Sprintf("parse mem field: %s failed: %v", field, err)) 116 | } 117 | } 118 | return stats 119 | } 120 | -------------------------------------------------------------------------------- /runner/perf/mem/mem_linux.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package mem 18 | 19 | import ( 20 | "fmt" 21 | "io/ioutil" 22 | "os" 23 | "path" 24 | "unsafe" 25 | ) 26 | 27 | func getStats(pid int) (Stats, error) { 28 | data, err := getSmaps(pid) 29 | if err != nil { 30 | return Stats{}, err 31 | } 32 | return parseSmaps(data), nil 33 | } 34 | 35 | func getSmaps(pid int) (string, error) { 36 | procMemFile := path.Join(fmt.Sprintf("/proc/%d/smaps_rollup", pid)) 37 | file, err := os.Open(procMemFile) 38 | if err != nil { 39 | return "", err 40 | } 41 | bytes, err := ioutil.ReadAll(file) 42 | if err != nil { 43 | return "", err 44 | } 45 | return b2s(bytes), nil 46 | } 47 | 48 | func b2s(b []byte) string { 49 | return *(*string)(unsafe.Pointer(&b)) 50 | } 51 | -------------------------------------------------------------------------------- /runner/perf/mem/mem_mock.go: -------------------------------------------------------------------------------- 1 | // Copyright 2021 CloudWeGo Authors 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build darwin || netbsd || freebsd || openbsd || dragonfly 16 | // +build darwin netbsd freebsd openbsd dragonfly 17 | 18 | package mem 19 | 20 | import "math/rand" 21 | 22 | func getStats(pid int) (Stats, error) { 23 | return Stats{ 24 | Rss: rand.Int63(), 25 | Pss: rand.Int63(), 26 | }, nil 27 | } 28 | -------------------------------------------------------------------------------- /runner/perf/mem/mem_test.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package mem 18 | 19 | import ( 20 | "testing" 21 | ) 22 | 23 | var testdata = `00400000-7ffc2c73a000 ---p 00000000 00:00 0 24 | [rollup] 25 | Rss: 93112 kB 26 | Pss: 92143 kB 27 | Pss_Anon: 84876 kB 28 | Pss_File: 7267 kB 29 | Pss_Shmem: 0 kB 30 | Shared_Clean: 980 kB 31 | Shared_Dirty: 0 kB 32 | Private_Clean: 61312 kB 33 | Private_Dirty: 30820 kB 34 | Referenced: 39056 kB 35 | Anonymous: 84876 kB 36 | LazyFree: 54056 kB 37 | AnonHugePages: 0 kB 38 | ShmemPmdMapped: 0 kB 39 | FilePmdMapped: 0 kB 40 | Shared_Hugetlb: 0 kB 41 | Private_Hugetlb: 0 kB 42 | Swap: 0 kB 43 | SwapPss: 0 kB 44 | Locked: 0 kB 45 | ` 46 | 47 | func TestParseSmaps(t *testing.T) { 48 | stats := parseSmaps(testdata) 49 | t.Logf("stats: %v", stats) 50 | if stats.Rss != 93112 { 51 | t.Fail() 52 | } 53 | if stats.Pss != 92143 { 54 | t.Fail() 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /runner/perf/recorder.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package perf 18 | 19 | import ( 20 | "context" 21 | "fmt" 22 | "log" 23 | "sync" 24 | 25 | "github.com/cloudwego/netpoll-benchmark/runner/perf/cpu" 26 | "github.com/cloudwego/netpoll-benchmark/runner/perf/mem" 27 | ) 28 | 29 | type Recorder struct { 30 | name string 31 | finish func() 32 | waiter sync.WaitGroup 33 | CpuUsage cpu.Usage 34 | MemUsage mem.Usage 35 | } 36 | 37 | func NewRecorder(name string) *Recorder { 38 | return &Recorder{ 39 | name: name, 40 | } 41 | } 42 | 43 | func (r *Recorder) Begin() { 44 | ctx, finish := context.WithCancel(context.Background()) 45 | r.finish = finish 46 | r.waiter.Add(2) 47 | go func() { 48 | defer r.waiter.Done() 49 | var err error 50 | r.CpuUsage, err = cpu.RecordUsage(ctx) 51 | if err != nil { 52 | log.Fatalf("recording cpu usage failed: %v", err) 53 | } 54 | }() 55 | go func() { 56 | defer r.waiter.Done() 57 | var err error 58 | r.MemUsage, err = mem.RecordUsage(ctx) 59 | if err != nil { 60 | log.Fatalf("recording mem usage failed: %v", err) 61 | } 62 | }() 63 | } 64 | 65 | func (r *Recorder) End() { 66 | r.finish() 67 | r.waiter.Wait() 68 | } 69 | 70 | func (r *Recorder) ReportString() string { 71 | output := "" 72 | output += fmt.Sprintf("[%s] CPU Usage: %s\n", r.name, r.CpuUsage) 73 | output += fmt.Sprintf("[%s] Mem Usage: %s\n", r.name, r.MemUsage) 74 | return output 75 | } 76 | 77 | func (r *Recorder) Report() { 78 | fmt.Print(r.ReportString()) 79 | } 80 | -------------------------------------------------------------------------------- /runner/processor.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package runner 18 | 19 | import ( 20 | "fmt" 21 | "strings" 22 | 23 | "github.com/cloudwego/netpoll-benchmark/runner/perf" 24 | ) 25 | 26 | const ( 27 | MaxActionSize = 6 28 | ActionBegin = "begin" 29 | ActionEnd = "end" 30 | ActionReport = "report" 31 | ) 32 | 33 | func ProcessRequest(report *perf.Recorder, req *Message) (resp *Message) { 34 | if len(req.Message) < MaxActionSize { 35 | switch req.Message { 36 | case ActionBegin: 37 | report.Begin() 38 | case ActionEnd: 39 | report.End() 40 | return &Message{ 41 | Message: ActionReport + report.ReportString(), 42 | } 43 | } 44 | } 45 | return &Message{ 46 | Message: req.Message, 47 | } 48 | } 49 | 50 | func ProcessResponse(resp *Message) { 51 | if strings.HasPrefix(resp.Message, ActionReport) { 52 | fmt.Print(resp.Message[len(ActionReport):]) 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /runner/reporter.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package runner 18 | 19 | import ( 20 | "fmt" 21 | "time" 22 | 23 | "github.com/montanaflynn/stats" 24 | ) 25 | 26 | func (c *Counter) Report(title string, totalns int64, concurrent int, total int64, echoSize int) error { 27 | ms, sec := int64(time.Millisecond), int64(time.Second) 28 | logInfo("took %d ms for %d requests", totalns/ms, c.Total) 29 | logInfo("requests total: %d, failed: %d", c.Total, c.Failed) 30 | 31 | var tps float64 32 | if totalns < sec { 33 | tps = float64(c.Total*sec) / float64(totalns) 34 | } else { 35 | tps = float64(c.Total) / (float64(totalns) / float64(sec)) 36 | } 37 | 38 | var costs = make([]float64, len(c.costs)) 39 | for i := range c.costs { 40 | costs[i] = float64(c.costs[i]) 41 | } 42 | tp99, _ := stats.Percentile(costs, 99) 43 | tp999, _ := stats.Percentile(costs, 99.9) 44 | 45 | var result string 46 | if tp999/1000 < 1 { 47 | result = fmt.Sprintf("[%s]: TPS: %.2f, TP99: %.2fus, TP999: %.2fus (b=%d Byte, c=%d, n=%d)", 48 | title, tps, tp99/1000, tp999/1000, echoSize, concurrent, total) 49 | } else { 50 | result = fmt.Sprintf("[%s]: TPS: %.2f, TP99: %.2fms, TP999: %.2fms (b=%d Byte, c=%d, n=%d)", 51 | title, tps, tp99/1000000, tp999/1000000, echoSize, concurrent, total) 52 | } 53 | logInfo(blueString(result)) 54 | return nil 55 | } 56 | 57 | const blue_layout = "\x1B[1;36;40m%s\x1B[0m" 58 | 59 | var infoTitle = blueString("Info: ") 60 | 61 | func blueString(s string) string { 62 | return fmt.Sprintf(blue_layout, s) 63 | } 64 | 65 | func logInfo(format string, a ...interface{}) { 66 | s := fmt.Sprintf(format, a...) 67 | fmt.Println(infoTitle + s) 68 | } 69 | -------------------------------------------------------------------------------- /runner/runner.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package runner 18 | 19 | import ( 20 | "context" 21 | "errors" 22 | "fmt" 23 | "log" 24 | "strings" 25 | "sync" 26 | "time" 27 | 28 | "github.com/cloudwego/netpoll-benchmark/runner/perf" 29 | ) 30 | 31 | type Runner struct { 32 | counter *Counter // 计数器 33 | timer *Timer // 计时器 34 | } 35 | 36 | func NewRunner() *Runner { 37 | r := &Runner{ 38 | counter: NewCounter(), 39 | timer: NewTimer(time.Microsecond), 40 | } 41 | return r 42 | } 43 | 44 | // 并发测试 45 | func (r *Runner) Run(title string, once EchoOnce, concurrent int, total int64, echoSize int) { 46 | logInfo("%s start ..., concurrent: %d, total: %d", blueString("["+title+"]"), concurrent, total) 47 | 48 | // === warmup === 49 | r.benching(once, concurrent, 10000, echoSize) 50 | 51 | // === starting === 52 | if _, err := once(&Message{Message: ActionBegin}); err != nil { 53 | log.Fatalf("beginning server failed: %v", err) 54 | } 55 | recorder := perf.NewRecorder(fmt.Sprintf("%s@Client", strings.ToUpper(title))) 56 | recorder.Begin() 57 | 58 | // === benching === 59 | start := r.timer.Now() 60 | r.benching(once, concurrent, total, echoSize) 61 | stop := r.timer.Now() 62 | r.counter.Report(title, stop-start, concurrent, total, echoSize) 63 | 64 | // == ending === 65 | recorder.End() 66 | if _, err := once(&Message{Message: ActionEnd}); err != nil { 67 | log.Fatalf("ending server failed: %v", err) 68 | } 69 | 70 | // === reporting === 71 | recorder.Report() 72 | fmt.Printf("\n\n") 73 | } 74 | 75 | func (r *Runner) benching(once EchoOnce, concurrent int, total int64, echoSize int) { 76 | var wg sync.WaitGroup 77 | wg.Add(concurrent) 78 | r.counter.Reset(total) 79 | 80 | // benching 81 | for i := 0; i < concurrent; i++ { 82 | go func() { 83 | defer wg.Done() 84 | 85 | ctx := context.Background() 86 | body := make([]byte, echoSize) 87 | req := &Message{Message: string(body)} 88 | for { 89 | idx := r.counter.Idx() 90 | if idx >= total { 91 | return 92 | } 93 | begin := r.timer.Now() 94 | err := r.echoWithTimeout(ctx, once, req) 95 | end := r.timer.Now() 96 | cost := end - begin 97 | r.counter.AddRecord(idx, err, cost) 98 | } 99 | }() 100 | } 101 | wg.Wait() 102 | r.counter.Total = total 103 | } 104 | 105 | // here is timeout wrapper which is necessary in rpc, timeout=1s. 106 | func (r *Runner) echoWithTimeout(ctx context.Context, once EchoOnce, req *Message) error { 107 | ctx, cancel := context.WithTimeout(ctx, time.Second) 108 | defer cancel() 109 | 110 | done := make(chan error, 1) 111 | go func() { 112 | _, err := once(req) 113 | done <- err 114 | }() 115 | 116 | select { 117 | case err := <-done: 118 | return err 119 | case <-ctx.Done(): 120 | return errors.New("echo timeout") 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /runner/svr/server.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package svr 18 | 19 | import ( 20 | "flag" 21 | 22 | "github.com/cloudwego/netpoll-benchmark/runner" 23 | ) 24 | 25 | func Serve(newer runner.ServerNewer) { 26 | initFlags() 27 | svr := newer(runner.Mode(mode)) 28 | svr.Run(network, address) 29 | } 30 | 31 | var ( 32 | address string 33 | mode int 34 | 35 | network string = "tcp" 36 | ) 37 | 38 | func initFlags() { 39 | flag.StringVar(&address, "addr", "", "client call address") 40 | flag.IntVar(&mode, "mode", 1, "1: echo, 2: idle, 3: mux") 41 | flag.Parse() 42 | } 43 | -------------------------------------------------------------------------------- /runner/timer.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 CloudWeGo Authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package runner 18 | 19 | import ( 20 | "sync" 21 | "sync/atomic" 22 | "time" 23 | ) 24 | 25 | func NewTimer(window time.Duration) *Timer { 26 | t := &Timer{window: window} 27 | t.refresh() 28 | return t 29 | } 30 | 31 | // 全局 Timer, 共享时间周期, 并在到期时执行回调 32 | type Timer struct { 33 | sync.Once 34 | now int64 35 | window time.Duration 36 | notify []func(now time.Time) 37 | } 38 | 39 | // refresh time 40 | func (t *Timer) refresh() { 41 | t.Do(func() { 42 | atomic.StoreInt64(&t.now, time.Now().UnixNano()) 43 | go func() { 44 | for now := range time.Tick(t.window) { 45 | atomic.StoreInt64(&t.now, now.UnixNano()) 46 | } 47 | }() 48 | }) 49 | } 50 | 51 | func (t *Timer) Window() time.Duration { 52 | return t.window 53 | } 54 | 55 | // Timer 为共享计时器, 减少系统时间调用 56 | func (t *Timer) Now() int64 { 57 | return atomic.LoadInt64(&t.now) 58 | } 59 | -------------------------------------------------------------------------------- /scripts/benchmark_client.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | n=5000000 4 | body=(1024) # 1KB 5 | concurrent=(100 500 1000) 6 | 7 | # no need to change 8 | repo=("net" "netpoll") 9 | ports=(7001 7002) 10 | 11 | . ./scripts/env.sh 12 | . ./scripts/build.sh 13 | 14 | # echo 15 | args=${1-1} 16 | benchmark "client" $args 17 | -------------------------------------------------------------------------------- /scripts/benchmark_server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | n=5000000 4 | body=(1024) # 1KB 5 | concurrent=(100 500 1000) 6 | 7 | # no need to change 8 | repo=("net" "netpoll" "gnet" "evio") 9 | ports=(7001 7002 7003 7004) 10 | 11 | . ./scripts/env.sh 12 | . ./scripts/build.sh 13 | 14 | # check args 15 | args=${1-1} 16 | if [[ "$args" == "3" ]]; then 17 | repo=("net" "netpoll" "gnet") 18 | fi 19 | 20 | # 1. echo 21 | benchmark "server" $args 22 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # clean 4 | rm -rf output/ && mkdir -p output/bin/ && mkdir -p output/log/ 5 | 6 | # build client 7 | go build -v -o output/bin/net_bencher ./net/client 8 | go build -v -o output/bin/netpoll_bencher ./netpoll/client 9 | 10 | # build server 11 | go build -v -o output/bin/net_reciever ./net 12 | go build -v -o output/bin/netpoll_reciever ./netpoll 13 | go build -v -o output/bin/gnet_reciever ./gnet 14 | go build -v -o output/bin/evio_reciever ./evio 15 | -------------------------------------------------------------------------------- /scripts/env.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # cpu binding 4 | nprocs=$(getconf _NPROCESSORS_ONLN) 5 | if [ $nprocs -lt 4 ]; then 6 | echo "Your environment should have at least 4 processors" 7 | exit 1 8 | elif [ $nprocs -gt 20 ]; then 9 | nprocs=20 10 | fi 11 | scpu=$((nprocs > 16 ? 4 : nprocs / 4)) # max is 4 cpus 12 | ccpu=$((nprocs-scpu)) 13 | scpu_cmd="taskset -c 0-$((scpu-1))" 14 | ccpu_cmd="taskset -c ${scpu}-$((ccpu-1))" 15 | if [ -x "$(command -v numactl)" ]; then 16 | # use numa affinity 17 | node0=$(numactl -H | grep "node 0" | head -n 1 | cut -f "4-$((3+scpu))" -d ' ' --output-delimiter ',') 18 | node1=$(numactl -H | grep "node 1" | head -n 1 | cut -f "4-$((3+ccpu))" -d ' ' --output-delimiter ',') 19 | scpu_cmd="numactl -C ${node0} -m 0" 20 | ccpu_cmd="numactl -C ${node1} -m 1" 21 | fi 22 | 23 | # benchmark, args1=server|client 24 | function benchmark() { 25 | . ./scripts/kill_servers.sh 26 | 27 | mode=${2-1} 28 | echo "benchmark mode=$mode" 29 | for b in ${body[@]}; do 30 | for c in ${concurrent[@]}; do 31 | for ((i = 0; i < ${#repo[@]}; i++)); do 32 | rp=${repo[i]} 33 | addr="127.0.0.1:${ports[i]}" 34 | 35 | # server start 36 | svr=${rp}_reciever 37 | if [[ "$1" == "client" ]]; then 38 | svr="net_reciever" 39 | fi 40 | nohup $scpu_cmd ./output/bin/${svr} -addr="$addr" -mode=$mode >>output/log/nohup.log 2>&1 & 41 | sleep 1 42 | echo "server $svr running with $scpu_cmd" 43 | 44 | # run client 45 | cli=${rp}_bencher 46 | if [[ "$1" == "server" ]]; then 47 | cli="net_bencher" 48 | fi 49 | echo "client $cli running with $ccpu_cmd" 50 | $ccpu_cmd ./output/bin/${cli} -name="$rp" -addr="$addr" -mode=$mode -b=$b -c=$c -n=$n 51 | 52 | # stop server 53 | pid=$(ps -ef | grep $svr | grep -v grep | awk '{print $2}') 54 | if [[ -n "$pid" ]]; then 55 | disown $pid 56 | kill -9 $pid 57 | fi 58 | 59 | sleep 1 60 | done 61 | done 62 | done 63 | } 64 | -------------------------------------------------------------------------------- /scripts/kill_servers.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ps -ef | grep reciever | grep -v grep 4 | pid=$(ps -ef | grep reciever | grep -v grep | awk '{print $2}') 5 | 6 | # kill if server exist 7 | if [[ -n "$pid" ]]; then 8 | kill -9 $pid 9 | fi 10 | -------------------------------------------------------------------------------- /scripts/reports/render_images.py: -------------------------------------------------------------------------------- 1 | import matplotlib.pyplot as plt 2 | import sys 3 | 4 | kind = "concurrent" 5 | 6 | 7 | # 0-name, 1-concurrency, 2-size, 3-qps, 6-p99, 7-p999 8 | def parse_data(file): 9 | import csv 10 | csv_reader = csv.reader(open(file)) 11 | lines = [] 12 | for line in csv_reader: 13 | lines.append(line) 14 | x_label, x_ticks = parse_x(lines=lines) 15 | print(x_label, x_ticks) 16 | 17 | y_qps = parse_y(lines=lines, idx=3) 18 | print(y_qps) 19 | plot_data(title="QPS (higher is better)", xlabel=x_label, ylabel="qps", x_ticks=x_ticks, ys=y_qps) 20 | 21 | y_p99 = parse_y(lines=lines, idx=4, times=1000) 22 | print(y_p99) 23 | plot_data(title="TP99 (lower is better)", xlabel=x_label, ylabel="latency(us)", x_ticks=x_ticks, ys=y_p99) 24 | 25 | y_p999 = parse_y(lines=lines, idx=5, times=1000) 26 | print(y_p999) 27 | plot_data(title="TP999 (lower is better)", xlabel=x_label, ylabel="latency(us)", x_ticks=x_ticks, ys=y_p999) 28 | 29 | 30 | # 并发相同比 size; size 相同比并发 31 | def parse_x(lines): 32 | l = len(lines) 33 | idx = 1 34 | x_label = "concurrency" 35 | if lines[0][1] == lines[l - 1][1]: 36 | idx = 2 37 | x_label = "echo size(Byte)" 38 | x_list = [] 39 | x_key = lines[0][0] 40 | for line in lines: 41 | if line[0] == x_key: 42 | x_list.append(int(line[idx])) 43 | return x_label, x_list 44 | 45 | 46 | def parse_y(lines, idx, times=1): 47 | y_dict = {} 48 | for line in lines: 49 | name = line[0] 50 | y_line = y_dict.get(name, []) 51 | n = float(line[idx]) * times 52 | y_line.append(int(n)) 53 | # y_line.append(int(line[idx])) 54 | y_dict[name] = y_line 55 | return y_dict 56 | 57 | 58 | # TODO 59 | color_dict = { 60 | "[netpoll]": "royalblue", 61 | "[net]": "darkorange", 62 | "[gnet]": "purple", 63 | "[evio]": "pink", 64 | } 65 | 66 | 67 | # ys={"$net":[]number} 68 | def plot_data(title, xlabel, ylabel, x_ticks, ys): 69 | plt.figure(figsize=(8, 5)) 70 | # bmh、ggplot、dark_background、fivethirtyeight 和 grayscale 71 | plt.style.use('grayscale') 72 | plt.title(title) 73 | 74 | plt.xlabel(xlabel) 75 | plt.ylabel(ylabel) 76 | 77 | # x 轴示数 78 | plt.xticks(range(len(x_ticks)), x_ticks) 79 | 80 | for k, v in ys.items(): 81 | color = color_dict.get(k) 82 | if color != "": 83 | plt.plot(v, label=k, linewidth=2, color=color) 84 | else: 85 | plt.plot(v, label=k, linewidth=2) 86 | 87 | # y 轴从 0 开始 88 | bottom, top = plt.ylim() 89 | plt.ylim(bottom=0, top=1.2 * top) 90 | 91 | plt.legend(prop={'size': 12}) 92 | plt.savefig("{0}_{1}.png".format(kind, title.split(" ")[0].lower())) 93 | # plt.show() 94 | 95 | 96 | if __name__ == '__main__': 97 | if len(sys.argv) > 1: 98 | kind = sys.argv[1] 99 | parse_data(file="{0}.csv".format(kind)) 100 | -------------------------------------------------------------------------------- /scripts/run_servers.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | . ./scripts/env.sh 4 | . ./scripts/build.sh 5 | . ./scripts/kill_servers.sh 6 | 7 | core=0 8 | for ((i = 0; i < ${#repo[@]}; i++)); do 9 | rp=${repo[i]} 10 | addr=":${ports[i]}" 11 | 12 | # server start 13 | nohup taskset -c $core-$(($core + 3)) ./output/bin/${rp}_reciever -addr="$addr" >>output/log/${rp}.log 2>&1 & 14 | echo "server $rp running at cpu $core-$(($core + 3))" 15 | core=$(($core + 4)) 16 | sleep 1 17 | done 18 | -------------------------------------------------------------------------------- /scripts/update_data.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function to_csv() { 4 | grep TPS "$1.log" | awk -F '[ :,]+' '{split($2,title,"m");split($6,a,"m");split($8,b,"m");print title[3]","substr($11,3)","substr($9,4)","$4","a[1]","b[1]}' >"$1.csv" 5 | } 6 | 7 | endpoint=("server" "client") 8 | 9 | for ((j = 0; j < ${#endpoint[@]}; j++)); do 10 | ep=${endpoint[j]} 11 | for ((i = 1; i <= 3; i++)); do 12 | filename="${ep}${i}" 13 | # benchmark 14 | echo "./scripts/benchmark_${ep}.sh ${i} > ${filename}.log" 15 | ./scripts/benchmark_${ep}.sh ${i} >"${filename}.log" 16 | 17 | # update data & images 18 | to_csv "${filename}" 19 | echo "python3 scripts/reports/render_images.py ${filename}" 20 | python3 scripts/reports/render_images.py "${filename}" 21 | mv "${filename}_qps.png" "${filename}_tp99.png" "${filename}_tp999.png" docs/images/ 22 | 23 | # clean 24 | # rm "${filename}.log" "${filename}.csv" 25 | done 26 | done 27 | 28 | # not used 29 | function to_table() { 30 | grep TPS output.log | awk -F '[ :,]+' '{print " "$2" 传输 "$4" "$6" "$8" "}' >"$1.txt" 31 | } 32 | --------------------------------------------------------------------------------