【C#/ASP.NET】Web APIのベンチマークを計測する

# 概要
クライアントが「Web APIをコールし、サーバから応答を得るまでの時間」を計測した。
具体的には、HttpClientとBenchmarkDotNetを使って、処理時間の集計(平均値・最小値・最大値を算出)し、集計結果をHTMLでエクスポートとした。
計測の手順と実装を掲載する。

# 動作環境
## クライアント
.NET Framework4.6.1
Nugetパッケージ
　・BenchmarkDotNet 0.13.0
　・NewtonsoftJson 13.0.1
## サーバー
ASP.NET Web API
.NET Framework4.6.1

# 計測予定のWebAPIの一覧

今回はGET/PUT/POST/DELETEを準備する。
下記表のWeb APIをサーバ側に実装する（自動生成コードをそのまま利用する)。

| HTTPメソッド | URL |
|:-|:-|
|GET   |http://localhost:19691/api/values   |
|POST   |http://localhost:19691/api/values   |
|PUT   |http://localhost:19691/api/values/{id}   |
|DELETE   |http://localhost:19691/api/values/{id}   |

# アプリケーションを作成する。

## クライアントを作成する
コンソールアプリケーションでソリューションを作成して、下記手順を実行する。

### 1. BenchmarkDotNetを適用する。
「BenchmarkDotNet」をNugetパッケージでインストールする。

### 2. 計測対象のメソッドを作成する。
「BenchmarkDotNet」で計測する対象メソッド、その他オプションは「属性」で設定する。
各属性の意味はコメントで補足説明する。

~~~C#:WebApiClient
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using BenchmarkDotNet.Attributes;
using Newtonsoft.Json;

namespace BenchMark
{
    [HtmlExporter]//Htmlエクスポート
    public class WebApiClient
    {
        private static readonly HttpClient Client = new HttpClient();
        private static string _jsonString;
        private static StringContent _data;
        
        [GlobalSetup]//初回セットアップ
        public void GlobalSetUp()
        {
            _jsonString = JsonConvert.SerializeObject("TestMessage");
            _data = new StringContent(_jsonString, Encoding.UTF8, mediaType: "application/json");
        }
        
        [Benchmark]//計測対象のメソッドに指定
        public async Task GetAsync()
        {
            var response = await Client.GetAsync("http://localhost:19691/api/values");
            response.EnsureSuccessStatusCode();
        }

        [Benchmark]
        public async Task PutAsync()
        {
            var response = await Client.PutAsync("http://localhost:19691/api/values/5", _data);
            response.EnsureSuccessStatusCode();
        }
        
        [Benchmark]
        public async Task PostAsync()
        {
            var response = await Client.PostAsync("http://localhost:19691/api/values", _data);
            response.EnsureSuccessStatusCode();
        }
        
        [Benchmark]
        public async Task DeleteAsync()
        {
            var response = await Client.DeleteAsync("http://localhost:19691/api/values/5");
            response.EnsureSuccessStatusCode();
        }
    }
}
~~~

### 3. メイン関数から計測対象をコールする。


~~~C#:Program.cs
using System.Threading.Tasks;
using BenchmarkDotNet.Running;
namespace BenchMark
{
    class Program
    {
        static void Main(string[] args)
        {
            //CheckOperation().GetAwaiter().GetResult();
            
            //計測を開始する
            BenchmarkRunner.Run<WebApiClient>();
        }

        /// <summary>
        /// ベンチマーク測定前の動作確認用
        /// </summary>
        static async Task CheckOperation()
        {
            var client = new WebApiClient();
            client.GlobalSetUp();
            await client.GetAsync();
            await client.PutAsync();
            await client.PostAsync();
            await client.DeleteAsync();
        }
    }
}
~~~

## サーバを作成する。
自動生成コード使って、Web　APIを準備する。
### 1. Web　アプリケーションでソリューションを作成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/156594/0931df07-8143-23d9-8bce-83f2570dbff2.png)

Web　APIを選ぶ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/156594/5ec5f466-a990-9911-d918-b73a2e3208fb.png)

### 2. Web　APIが自動生成されたことを確認する。
「ソリューションエクスプローラー」
→「Controllers」
　　ValuesControllers.csを開く。

~~~C#:ValuesControllers.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;

namespace HttpServerBenchMark.Controllers
{
    public class ValuesController : ApiController
    {
        // GET api/values
        public IEnumerable<string> Get()
        {
            return new string[] { "value1", "value2" };
        }

        // GET api/values/5
        public string Get( int id )
        {
            return "value";
        }

        // POST api/values
        public void Post( [FromBody]string value )
        {
        }

        // PUT api/values/5
        public void Put( int id, [FromBody]string value )
        {
        }

        // DELETE api/values/5
        public void Delete( int id )
        {
        }
    }
}
~~~

# パフォーマンス計測

## 1. サーバーを実行する。

## 2. クライアントを実行する。
※Releaseビルドで実行すること。
計測が完了することを待つ。

### 計測開始時のターミナル出力

~~~
// Validating benchmarks:
// ***** BenchmarkRunner: Start   *****
// ***** Found 4 benchmark(s) in total *****
// ***** Building 1 exe(s) in Parallel: Start   *****
:
:
~~~

### 計測終了時のターミナル出力

~~~
// ***** BenchmarkRunner: End *****
// ** Remained 0 benchmark(s) to run **
Run time: 00:01:59 (119.94 sec), executed benchmarks: 4

Global total time: 00:02:02 (122.68 sec), executed benchmarks: 4
~~~

## 3. 集計結果を確認する。
ターミナル出力で「Summary」を探して確認する。

### 結果サマリーを確認する

~~~
// * Summary *

BenchmarkDotNet=v0.13.0, OS=Windows 10.0.19043.985 (21H1/May2021Update)
Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores
  [Host]     : .NET Framework 4.8 (4.8.4341.0), X86 LegacyJIT
  DefaultJob : .NET Framework 4.8 (4.8.4341.0), X86 LegacyJIT

|      Method |     Mean |     Error |    StdDev |   Median |
|------------ |---------:|----------:|----------:|---------:|
|    GetAsync | 1.921 ms | 0.1567 ms | 0.4469 ms | 1.749 ms |
|    PutAsync | 1.495 ms | 0.0319 ms | 0.0926 ms | 1.494 ms |
|   PostAsync | 1.472 ms | 0.0291 ms | 0.0638 ms | 1.474 ms |
| DeleteAsync | 1.281 ms | 0.0256 ms | 0.0455 ms | 1.293 ms |
~~~

### ファイルのExportを確認する。
今回はHTMLファイル出力しているため、ターミナルでExport先を確認する。

~~~
// ***** BenchmarkRunner: Finish  *****

// * Export *
  BenchmarkDotNet.Artifacts\results\BenchMark.WebApiClient-report.csv
  BenchmarkDotNet.Artifacts\results\BenchMark.WebApiClient-report-github.md
  BenchmarkDotNet.Artifacts\results\BenchMark.WebApiClient-report.html
~~~

ファイルエクスプローラで下記パスに移動する。
ソリューションのパス\bin\Release\BenchmarkDotNet.Artifacts
下記画像のファイルが存在する。
「BenchMark.WebApiClient-20210606-231210.log（※）」に、ターミナル出力と同様の内容が記録されている。
※ソリューション名-クラス名-実行日時.logって命名の様子。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/156594/c69d0d2c-261c-185a-07f3-9fd14f313b8c.png)

ソリューションのパス\bin\Release\BenchmarkDotNet.Artifacts\resultsに移動する。
BenchmarkDotNet.Artifacts\results\BenchMark.WebApiClient-report.htmlを開く。
下図のようにサマリーと同様の内容が表示される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/156594/17a93143-58ee-8c7c-5a48-d742ba392c25.png)

logファイルとResultフォルダを控えておけば、報告資料や調査結果にそのまま利用できそう。


# 参考

| No | リンク | 説明 |
|:-|:-|:-|
|1   |[【.NET/C#】メソッドのパフォーマンスを簡単に集計するライブラリの紹介][1]   |過去に書いたQuiitaの記事。内容はBenchmarkDotNetの紹介と使い方に関して記載した。|
|2   |[BenchmarkDotNetの公式ドキュメント][2]   |公式ドキュメントのリンク。   |
|3   |[Github:本記事のソース][3]   |本記事のソースをGithubに公開してます。   |


[1]:https://qiita.com/SY81517/items/79f6c5905e758279831a
[2]:https://benchmarkdotnet.org/articles/overview.html
[3]:https://github.com/SY81517/Quiita/tree/master/TryHttpBenchMark