zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    转发:前端新人少走弯路
    react 表单(受控组件和非受控组件)
    axios封装的拦截器的应用
    react与redux的一点心得(理解能力有限,蜗牛进度)
    redux一些自习时候自己写的的单词
    react路由的跳转和传参
    学以致用,react学习前奏准备阶段
    原型,原型链,给予原型和class的继承
    rpm安装和卸载
    .net项目发布到iis
  • 原文地址:https://www.cnblogs.com/cwsheng/p/14800870.html
Copyright © 2011-2022 走看看