EventSource的自定義實現


前言:

 前面兩篇文章都介紹了.NET Core 性能診斷工具,其中診斷工具都用到了EventCounters來實時的收集服務器性能指標。

 那么收集指標能否自己定義呢?

一、What's EventCounters ?

 EventCounters 是一些 .NET Core API,用於輕量級、跨平台、准實時性能指標收集。 EventCounters 添加為 Windows 上的 .NET 框架的“性能計數器”的跨平台替代。

 EventCounters 作為 EventSource 的一部分實時自動定期推送到偵聽器工具。 與 EventSource 上所有其他事件一樣,可以通過 EventListener 和 EventPipe 在進程內和進程外使用它們。

 

  EventCounters 分為兩類計數器: 某些計數器用於計算“比率”的值,例如異常總數、GC 總數和請求總數。 其他計數器是“快照”值,例如堆使用情況、CPU 使用率和工作集大小。 在這兩個類別的計數器中,各有兩種類型的計數器,由獲取值的方式區分。 輪詢計數器通過回調檢索其值,非輪詢計數器直接在計數器實例上設置其值。  

 計數器由以下實現表示:

  • EventCounter:記錄一組值。 EventCounter.WriteMetric 方法將新值添加到集。 在每個間隔中,將計算集的統計摘要,如最小值、最大值和平均值。

    EventCounter 用於描述一組離散的操作。 常見用法包括監視最近 IO 操作的平均大小(以字節為單位)或一組金融交易的平均貨幣價值。

  • IncrementingEventCounter:記錄每個時間間隔的運行總計,使用 Increment 方法添加到總計。

   例如,如果在一段間隔內調用三次 Increment(),其值分別為 12 和 5,則此間隔的計數器值將報告運行總計 8。 用於測量操作發生的頻率,例如每秒處理的請求數。

  • IncrementingPollingCounter:使用回調來確定報告的值。 在每個時間間隔中,調用用戶提供的回調函數,然后返回值用作計數器值。

   例如獲取磁盤上的當前可用字節。 它還可用於報告應用程序可按需計算的自定義統計信息。 示例包括報告最近請求延遲的第 95 個百分位,或緩存的當前命中或錯過比率。

  • PollingCounter:使用回調來確定報告的增量值。 對於每個時間間隔,調用回調,然后當前調用與最后一個調用之間的差值是報告的值。

   如果不可在每次發生事件時調用 API,但可以查詢事件總數,則此計數器很有用。 例如,可以報告每秒寫入文件的字節數,即使每次寫入字節時沒有通知。

  了解各種計數器后,我們就可以開始實現自己的EventCounters來跟蹤各種指標,並在自己的程序中使用

二、自定義實現EventSource

 自定義實現一個獲取.NET Core Api項目的請求總數量、獲取當前進程內存占用、請求數量、請求平均耗時情況的EventSource。

 1、添加:ApiEventCounterSource.cs 實現EventSource,並添加計數器用於獲取對應數據

using System;
using System.Diagnostics.Tracing;
using System.Threading;

namespace AuditLogDemo.EventSources
{
    /// <summary>
    /// Api.EventCounter 事件源
    /// </summary>
    [EventSource(Name = "Api.EventCounter")]
    public sealed class ApiEventCounterSource : EventSource
    {
        public static readonly ApiEventCounterSource Log = new ApiEventCounterSource();

        private EventCounter _requestCounter;

        private PollingCounter _workingSetCounter;

        private PollingCounter _totalRequestsCounter;

        private IncrementingPollingCounter _incrementingPollingCounter;

        private long _totalRequests;

        private ApiEventCounterSource()
        {
        }


        protected override void OnEventCommand(EventCommandEventArgs command)
        {
            if (command.Command == EventCommand.Enable)
            {
                //請求響應耗時
                _requestCounter = new EventCounter("request-time", this)
                {
                    DisplayName = "Request Processing Time",
                    DisplayUnits = "ms"
                };

                //內存占用
                _workingSetCounter = new PollingCounter("working-set", this, () => (double)(Environment.WorkingSet / 1_000_000))
                {
                    DisplayName = "Working Set",
                    DisplayUnits = "MB"
                };

                //總請求量
                _totalRequestsCounter = new PollingCounter("total-requests", this, () => Volatile.Read(ref _totalRequests))
                {
                    DisplayName = "Total Requests",
                    DisplayUnits = ""
                };

                //單位時間請求速率
                _incrementingPollingCounter = new IncrementingPollingCounter("Request Rate", this, () =>
                {
                    return Volatile.Read(ref _totalRequests);
                })
                {
                    DisplayName = "Request Rate",
                    DisplayUnits = "次/s",
                    //時間間隔1s
                    DisplayRateTimeScale = new TimeSpan(0, 0, 1)
                };
            }
        }

        public void Request(string url, float elapsedMilliseconds)
        {
            //更新請求數量(保證線程安全)
            Interlocked.Increment(ref _totalRequests);

            //寫入指標值(請求處理耗時)
            _requestCounter?.WriteMetric(elapsedMilliseconds);

        }

        protected override void Dispose(bool disposing)
        {
            _requestCounter?.Dispose();
            _requestCounter = null;

            _workingSetCounter?.Dispose();
            _workingSetCounter = null;

            _totalRequestsCounter?.Dispose();
            _totalRequestsCounter = null;

            _incrementingPollingCounter?.Dispose();
            _incrementingPollingCounter = null;

            base.Dispose(disposing);
        }
    }
}

 2、添加過濾器:ApiRequestTimeFilter調用ApiEventCounterSource方法

public class ApiRequestTimeFilterAttribute : ActionFilterAttribute
{
    readonly Stopwatch _stopwatch = new Stopwatch();
    public override void OnActionExecuting(ActionExecutingContext context)
    {
        base.OnActionExecuting(context);
        //開啟耗時記錄
        _stopwatch.Start();
    }
    public override void OnResultExecuted(ResultExecutedContext context)
    {
        //關閉耗時記錄
        _stopwatch.Stop();
        //調用方法記錄耗時 
     ApiEventCounterSource.Log.Request(context.HttpContext.Request.GetDisplayUrl(), _stopwatch.ElapsedMilliseconds);
} }

 3、啟用過濾器

services.AddControllers(options =>
{    
    options.Filters.Add(typeof(ApiRequestTimeFilterAttribute));
});

三、使用 EventCounters

 1、進程外使用:

  a) 使用全局診斷工具:

   dotnet-counters、dotnet-trace、dotnet-monitor 使用大體相同

  • 使用 dotnet-counters ps 命令來顯示可監視的進程的列表:      
  • 使用命令獲取計數器實時結果:通過--counters參數指定計數器范圍
     dotnet-counters monitor --process-id 進程id --counters Api.EventCounter,System.Runtime[cpu-usage]

  運行結果如下:

    

  b) 自實現工具:

   1、添加一個:EventPipeProvider 指定名稱為:Api.EventCounter

var providers = new List<EventPipeProvider>()
{
    new EventPipeProvider("Api.EventCounter",EventLevel.Informational,(long)ClrTraceEventParser.Keywords.None,
                                new Dictionary<string, string>() {{ "EventCounterIntervalSec", "1" }})
};

   2、運行效果如下:

    

 2、進程內使用:

  實現EventListener,獲取計數器值

public class ApiEventListener : EventListener
{
    protected override void OnEventSourceCreated(EventSource eventSource)
    {
        if (!eventSource.Name.Equals("Api.EventCounter"))
        {
            return;
        }

        EnableEvents(eventSource, EventLevel.Verbose, EventKeywords.All, new Dictionary<string, string>()
        {
            ["EventCounterIntervalSec"] = "1"
        });
    }

    protected override void OnEventWritten(EventWrittenEventArgs eventData)
    {
        if (!eventData.EventName.Equals("Api.EventCounter"))
        {
            return;
        }

        for (int i = 0; i < eventData.Payload.Count; ++i)
        {
            if (eventData.Payload[i] is IDictionary<string, object> eventPayload)
            {
                var (counterName, counterValue) = GetRelevantMetric(eventPayload);
                Console.WriteLine($"{counterName} : {counterValue}");
            }
        }
    }

    /// <summary>
    /// 計數器名稱和計數器值
    /// </summary>
    /// <param name="eventPayload"></param>
    /// <returns></returns>
    private static (string counterName, string counterValue) GetRelevantMetric(IDictionary<string, object> eventPayload)
    {
        var counterName = "";
        var counterValue = "";

        if (eventPayload.TryGetValue("DisplayName", out object displayValue))
        {
            counterName = displayValue.ToString();
        }
        if (eventPayload.TryGetValue("Mean", out object value) ||
            eventPayload.TryGetValue("Increment", out value))
        {
            counterValue = value.ToString();
        }

        return (counterName, counterValue);
    }
}

 

四、總結

 自定義實現EventSource可用於當前.dotnet 未提供的指標擴展、也可用於業務系統指定指標實現,等着去解鎖使用。

 當然dotnet 已經提供了多種性能計數器:供一般情況使用。已知EventCounters


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM