[.NET 6] 通過 MiniProfiler,診斷 ASP.NET Core Web API 效能

以往我都使用 dotTrace 診斷 .NET 應用程式的使用狀況,可以得知執行時間、佔用記憶體、SQL Command 等。

但,NET Core 沒有支援 SQL ETW,代表攔截不到有關 SQL 的命令,於是我得尋求其它解方,MiniProfiler 則是這次的調查對象。

 

Rider Profile Test

使用 dotTrace 需要配置一些設定,Rider 內建 Profile Test 節省了不少配置的動作,透過這個功能,不用額外埋診斷點,很快地知道這段程式碼花了多久時間,用了多少記憶體,這對於效能診斷相當的有效

在測試直接跑效能診斷,下圖是在 .NET Fx 使用 EF6

 

查看每一個方法的堆疊,所花費的時間

 

查看記憶體使用量

 

好工具不用嗎

 

估計,NET Core 因為跨平台所以沒有支援 SQL ETW,為了彌補這段,接下來,我採用 MiniProfiler

https://www.jetbrains.com/help/profiler/SQL_Client.html

有關 dotTrace 的配置可以參考

https://dotblogs.com.tw/yc421206/2018/01/19/jetbrains_dottrace_profiler_iis_analysis

MiniProfiler

MiniProfiler 是一套針對 .NET, Ruby, Go and Node.js 效能分析的輕量級套件。可以針對網站台頁面的響應及資料存取花費時間(支援 EF、EF Core)的結果呈現在頁面上,這比用 SQL Profiler 來的友善許多。

官網:https://miniprofiler.com/

多年前,曾經寫過 MiniProfiler 的使用方式

https://dotblogs.com.tw/yc421206/2016/06/11/dapper_miniprofiler_integrations_sql_command_trace

 

開發環境

  • Windows 11
  • Rider 2021.3.2
  • ASP.NET Core 6 Web API
  • EF Core 6
  • MiniProfiler 4.2.22

新增 ASP.NET Core 站台

新增一個 ASP .NET Core Web API

安裝以下套件

dotnet add package MiniProfiler.AspNetCore.Mvc
dotnet add iniProfiler.EntityFrameworkCore
dotnet add package Faker.Net

 

新增 EF Core 專案,這裡我借助上一篇的設定 EF Core 大量資料處理 for EFCore.BulkExtensions | 余小章 @ 大內殿堂 - 點部落 (dotblogs.com.tw)

再調整成我需要的樣子

 

連線字串,它在 launchSettings.json,基本上,因為這裡有連線字串,這個檔案不應該上版控,不過,我為了方便演示,所以還是把它放上來了

 

避免機密資料上版控,你可以參考以下作法:

 

MiniProfile Code

開始之前,我們可以替 MiniProfiler 加入分類,下圖出自 MiniProfiler for .NET : How-To Profile Code

設置 MiniProfiler 後,有多種方法可以分析代碼。MiniProfiler 通常設置為應用程序的每個“動作”(例如 HTTP 請求、啟動或某些作業)有 1 個分析器。在該分析器內部,有一些步驟。在步驟內部,您還可以有自定義時間。一般結構是:

簡單來講,就是讓報表看起來更有結構

 

接下來,加入 ValuesController,為了演練不要花太多時間,這裡我就直接讓我的 Controller 直接摸 EF 的DbContext 了,如果是在正式的專案,我不會這樣做

[Route("[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    private readonly IDbContextFactory<EmployeeDbContext> _employeeDbContextFactory;

    public ValuesController(IDbContextFactory<EmployeeDbContext> employeeDbContextFactory)
    {
        this._employeeDbContextFactory = employeeDbContextFactory;
    }

    /// <summary>
    ///     Get Api
    /// </summary>
    /// <returns></returns>

    // GET api/values
    [HttpGet]
    public async Task<IActionResult> Get(CancellationToken cancel = default)
    {
        using (MiniProfiler.Current.Step("查詢資料庫"))
        {
            await using var db = await this._employeeDbContextFactory.CreateDbContextAsync(cancel);
            return this.Ok(await db.Employees.AsTracking().ToListAsync(cancel));
        }
    }

    // POST api/values
    [HttpPost]
    public async Task<IActionResult> Post(CancellationToken cancel = default)
    {
        using (MiniProfiler.Current.Step("異動資料庫"))
        {
            await using var db = await this._employeeDbContextFactory.CreateDbContextAsync(cancel);

            var toDb = new Employee
            {
                Id = Guid.NewGuid(),
                CreateAt = DateTimeOffset.Now,
                CreateBy = Faker.Name.FullName(),
                Age = Faker.RandomNumber.Next(1, 100),
                Name = Faker.Name.Suffix(),
            };
            db.Employees.Add(toDb);
            await db.SaveChangesAsync(cancel);
            return this.Ok(toDb);
        }
    }
}

 

設定 MiniProfiler

設定 MiniProfiler 的 DI 設定,MiniProfiler 的位置為 

  • profiler/results-index
  • profiler/results-list
  • profiler/results

 

builder.Services.AddSwaggerGen();
builder.Services.AddMiniProfiler(o => o.RouteBasePath = "/profiler")
       .AddEntityFramework();
builder.Services.AddAppEnvironment();
builder.Services.AddEntityFramework();

AddAppEnvironment() 和 AddEntityFramework() 的設定,不是本篇的重點,詳情請參考以下:

sample.dotblog/AppDependencyInjectionExtensions.cs at master · yaochangyu/sample.dotblog (github.com)

 

加入 MiniProfiler 的 Middleware 

app.UseMiniProfiler()

 

啟動容器

Rider 跟容器有很不錯的整合,下圖有兩個地方可以啟動容器

 

完成之後啟動站台

 

執行 values,如下圖:

 

訪問 profiler/results,我們可以得到整個 Request 所花費的時間,最重要的是,連 SQL Command 都監聽到了

 

MiniProfiler 整合 Swagger UI

加入 Swagger UI 內容 sample.dotblog/index.html at master · yaochangyu/sample.dotblog (github.com) 

這個檔案我是參考下篇的作法

ASP.NET Core WebAPI中的分析工具MiniProfiler - LamondLu - 博客园 (cnblogs.com)

然後換成讀取這個檔案

app.UseSwaggerUI(c =>
{
    c.RoutePrefix = "swagger";
    c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
    c.IndexStream = () => typeof(Program).GetTypeInfo()
                                         .Assembly
                                         .GetManifestResourceStream("Lab.NETMiniProfiler.ASPNetCore6.index.html");
});

 

Lab.NETMiniProfiler.ASPNetCore6 是 Namespace

再運行一次,應該就能看到 Swagger UI 有東西浮出來,點看來看就可以看到比較詳細的內容

 

結論

MiniProfiler 補足了 Rider Profile Test / dotTrace 無法監聽 .NET Core SQL Command 的不足,使用上也相當的容易,當我們需要改善 SQL 效能的時候就方便許多,其他 .NET 框架的設定方式參考以下

MiniProfiler for .NET : MiniProfiler for .NET (including ASP.NET & ASP.NET Core)

範例位置

sample.dotblog/Benchmark/Lab.NETMiniProfiler at master · yaochangyu/sample.dotblog (github.com)

若有謬誤,煩請告知,新手發帖請多包涵


Microsoft MVP Award 2010~2017 C# 第四季
Microsoft MVP Award 2018~2022 .NET

Image result for microsoft+mvp+logo