教學課程:在 .NET Core 中使用 EventCounters 測量效能
本文適用於:✔️ .NET Core 3.0 SDK 與更新版本
在本教學課程中,您將了解如何使用 EventCounter 來測量高頻率事件的效能。 您可以使用各種官方 .NET Core 套件、第三方提供者發佈的可用計數器,或建立自己的監視計量。
在此教學課程中,您需要:
- 實作 EventSource。
- 使用 dotnet-counters 監視計數器。
必要條件
教學課程使用:
- .NET Core 3.1 SDK 或更新版本。
- 用來監視事件計數器的 dotnet-counters。
- 要診斷的範例偵錯目標應用程式。
取得來源
應用程式範例會是監視的基礎。 範例 ASP.NET Core 存放庫可從範例瀏覽器取得。 請下載 ZIP 檔案、下載後將其解壓縮,然後在您慣用的 IDE 中予以開啟。 建置並執行應用程式,以確保其運作正常,然後停止應用程式。
實作 EventSource
對於每隔幾毫秒發生的事件,您會希望每個事件的額外負荷較低 (小於毫秒)。 否則,對效能的影響會很顯著。 記錄事件表示將某些內容寫入磁碟。 如果磁碟的速度不夠快,您將會遺失事件。 您需要記錄事件本身以外的解決方案。
處理大量事件時,了解每個事件的量值並無助益。 多數時候,您只需要一些來自量值的統計資料。 因此,您可以在處理序本身取得統計資料,然後每隔一段時間寫入一次事件以回報統計資料,而這就是 EventCounter 的功能。
以下是如何實作 System.Diagnostics.Tracing.EventSource 的範例。 建立名為 MinimalEventCounterSource.cs 的新檔案,並使用程式碼片段作為其來源:
using System.Diagnostics.Tracing;
[EventSource(Name = "Sample.EventCounter.Minimal")]
public sealed class MinimalEventCounterSource : EventSource
{
public static readonly MinimalEventCounterSource Log = new MinimalEventCounterSource();
private EventCounter _requestCounter;
private MinimalEventCounterSource() =>
_requestCounter = new EventCounter("request-time", this)
{
DisplayName = "Request Processing Time",
DisplayUnits = "ms"
};
public void Request(string url, long elapsedMilliseconds)
{
WriteEvent(1, url, elapsedMilliseconds);
_requestCounter?.WriteMetric(elapsedMilliseconds);
}
protected override void Dispose(bool disposing)
{
_requestCounter?.Dispose();
_requestCounter = null;
base.Dispose(disposing);
}
}
EventSource.WriteEvent 這行是 EventSource 部分,不是 EventCounter 的一部分;寫入是為了顯示您可以與事件計數器一併記錄訊息。
新增動作篩選
範例原始程式碼是 ASP.NET Core 專案。 您可在全域新增動作篩選,以記錄要求時間總計。 建立名為 LogRequestTimeFilterAttribute.cs 的新檔案,並使用下列程式碼:
using System.Diagnostics;
using Microsoft.AspNetCore.Http.Extensions;
using Microsoft.AspNetCore.Mvc.Filters;
namespace DiagnosticScenarios
{
public class LogRequestTimeFilterAttribute : ActionFilterAttribute
{
readonly Stopwatch _stopwatch = new Stopwatch();
public override void OnActionExecuting(ActionExecutingContext context) => _stopwatch.Start();
public override void OnActionExecuted(ActionExecutedContext context)
{
_stopwatch.Stop();
MinimalEventCounterSource.Log.Request(
context.HttpContext.Request.GetDisplayUrl(), _stopwatch.ElapsedMilliseconds);
}
}
}
動作篩選會在要求開始時啟動 Stopwatch,在完成之後停止,並擷取已耗用時間。 毫秒總計會記錄到 MinimalEventCounterSource
單一執行個體。 為套用此篩選,您必須將其新增至篩選集合。 在 Startup.cs 檔案中更新 ConfigureServices
方法,以包含此篩選。
public void ConfigureServices(IServiceCollection services) =>
services.AddControllers(options => options.Filters.Add<LogRequestTimeFilterAttribute>())
.AddNewtonsoftJson();
監視事件計數器
在 EventSource 和自訂動作篩選上實作之後,建置並啟動應用程式。 您已將計量記錄到 EventCounter,但除非您從中存取統計資料,否則這並無用處。 若要取得統計資料,您必須建立引發頻率與事件一致的計時器,以及用來擷取事件的接聽程式,以啟用 EventCounter。 若要這樣做,您可以使用 dotnet-counters。
使用 dotnet-counters ps 命令來顯示可監視的 .NET 處理序清單。
dotnet-counters ps
您可以利用 dotnet-counters ps
命令輸出中的處理序識別碼,使用下列 dotnet-counters monitor
命令開始監視事件計數器:
dotnet-counters monitor --process-id 2196 --counters Sample.EventCounter.Minimal,Microsoft.AspNetCore.Hosting[total-requests,requests-per-second],System.Runtime[cpu-usage]
當 dotnet-counters monitor
命令執行時,請在瀏覽器按住 F5,以開始對 https://localhost:5001/api/values
端點發出連續要求。 幾秒後,按 q 停止
Press p to pause, r to resume, q to quit.
Status: Running
[Microsoft.AspNetCore.Hosting]
Request Rate / 1 sec 9
Total Requests 134
[System.Runtime]
CPU Usage (%) 13
[Sample.EventCounter.Minimal]
Request Processing Time (ms) 34.5
dotnet-counters monitor
命令很適合用於主動監視。 不過,建議您收集這些診斷計量,以供後續處理和分析。 為此,請使用 dotnet-counters collect
命令。 collect
參數命令類似於 monitor
命令,但接受額外的幾個參數。 您可以指定所需的輸出檔案名稱和格式。 針對名為 diagnostics.json 的 JSON 檔案,請使用下列命令:
dotnet-counters collect --process-id 2196 --format json -o diagnostics.json --counters Sample.EventCounter.Minimal,Microsoft.AspNetCore.Hosting[total-requests,requests-per-second],System.Runtime[cpu-usage]
同樣地,當命令執行時,請在瀏覽器按住 F5,以開始對 https://localhost:5001/api/values
端點發出連續要求。 幾秒後,按 q 停止。 diagnostics.json 檔案已寫入。 不過,寫入的 JSON 檔案不會縮排;為方便閱讀,此處顯示縮排形式。
{
"TargetProcess": "DiagnosticScenarios",
"StartTime": "8/5/2020 3:02:45 PM",
"Events": [
{
"timestamp": "2020-08-05 15:02:47Z",
"provider": "System.Runtime",
"name": "CPU Usage (%)",
"counterType": "Metric",
"value": 0
},
{
"timestamp": "2020-08-05 15:02:47Z",
"provider": "Microsoft.AspNetCore.Hosting",
"name": "Request Rate / 1 sec",
"counterType": "Rate",
"value": 0
},
{
"timestamp": "2020-08-05 15:02:47Z",
"provider": "Microsoft.AspNetCore.Hosting",
"name": "Total Requests",
"counterType": "Metric",
"value": 134
},
{
"timestamp": "2020-08-05 15:02:47Z",
"provider": "Sample.EventCounter.Minimal",
"name": "Request Processing Time (ms)",
"counterType": "Metric",
"value": 0
},
{
"timestamp": "2020-08-05 15:02:47Z",
"provider": "System.Runtime",
"name": "CPU Usage (%)",
"counterType": "Metric",
"value": 0
},
{
"timestamp": "2020-08-05 15:02:48Z",
"provider": "Microsoft.AspNetCore.Hosting",
"name": "Request Rate / 1 sec",
"counterType": "Rate",
"value": 0
},
{
"timestamp": "2020-08-05 15:02:48Z",
"provider": "Microsoft.AspNetCore.Hosting",
"name": "Total Requests",
"counterType": "Metric",
"value": 134
},
{
"timestamp": "2020-08-05 15:02:48Z",
"provider": "Sample.EventCounter.Minimal",
"name": "Request Processing Time (ms)",
"counterType": "Metric",
"value": 0
},
{
"timestamp": "2020-08-05 15:02:48Z",
"provider": "System.Runtime",
"name": "CPU Usage (%)",
"counterType": "Metric",
"value": 0
},
{
"timestamp": "2020-08-05 15:02:50Z",
"provider": "Microsoft.AspNetCore.Hosting",
"name": "Request Rate / 1 sec",
"counterType": "Rate",
"value": 0
},
{
"timestamp": "2020-08-05 15:02:50Z",
"provider": "Microsoft.AspNetCore.Hosting",
"name": "Total Requests",
"counterType": "Metric",
"value": 134
},
{
"timestamp": "2020-08-05 15:02:50Z",
"provider": "Sample.EventCounter.Minimal",
"name": "Request Processing Time (ms)",
"counterType": "Metric",
"value": 0
}
]
}