65.9K
CodeProject 正在变化。 阅读更多。
Home

Asp.Net:无需使用 Windows 性能计数器即可监控性能。

starIconstarIconstarIconstarIconstarIcon

5.00/5 (14投票s)

2017年2月15日

CPOL

7分钟阅读

viewsIcon

28509

用于监控 Asp.Net Web Api 2 和 MVC5 应用程序性能的开源框架,无需使用 Windows 性能计数器,可自动收集、存储和可视化性能计数器数据。

引言

使用 Windows 性能计数器是监控 Web 应用程序性能的常用方法。然而,有时可能无法访问这些 Windows 基础结构。例如,如果应用程序部署在共享托管计划上,您将无法访问 IIS 或 OS,或者没有特权访问权限。

此处介绍的 Perfon.Net 在这种情况下有助于监控 Web Api 2 或 MVC5 应用程序的基本性能计数器。它可以插入到 Asp.Net 应用程序中收集性能指标。它还提供了 REST API 和内置的 HTML 仪表板 UI,方便您远程可视化性能计数器。

下面我们将介绍 Web Api 和 MVC5 基础结构如何帮助我们测量 Web 应用程序的几个重要特征。之后,我们将通过示例检查 Perfon.Net。

源代码在 GitHub 上,地址为:https://github.com/magsoft2/Perfon.Net

Nuget 包也已可用:https://nuget.net.cn/packages/Perfon.WebApi/

代码概述

这是 Web 应用程序性能中最有趣的指标:

  • 每秒请求数
  • 每秒发送/接收的请求字节数
  • 状态码为“错误”的请求数
  • 轮询周期内的平均请求处理时间
  • 轮询周期内的最大请求处理时间
  • 请求处理过程中发生的异常数
  • CPU 使用率(%)
  • GC 0、1、2 代的收集次数

收集通用 .Net 指标

收集通用 .Net 指标非常容易。.Net 框架提供了静态方法 GC.CollectionCount(gen_number),该方法返回从开始到当前的 GC 垃圾回收次数。Perfon.Net 显示了轮询周期开始和结束值之间的差值,以便我们知道在轮询周期内发生了多少次 GC 收集。

要获取 CPU 时间以及上次收集后剩余的字节数(已知当前应用程序域引用),我们需要按以下方式启用此监控:

AppDomain.MonitoringIsEnabled = true;

之后,我们可以使用:

AppDomain.CurrentDomain.MonitoringSurvivedMemorySize;
AppDomain.CurrentDomain.MonitoringTotalProcessorTime.TotalMilliseconds/Environment.ProcessorCount

MonitoringTotalProcessorTime.TotalMilliseconds 返回的是程序启动以来的总时间,因此如果我们想知道每个轮询周期的百分比值,我们需要用新的值减去上一个值,然后除以轮询周期。此外,它还应按核心数(由 Environment.ProcessorCount 返回)进行标准化,从而使值范围在 0-100% 之间。否则,对于具有 4 个核心的计算机,您可能会得到 400% 的值。

收集 Web Api 性能指标

这有点棘手。以下是 Microsoft 海报中关于 Web Api 2 管道的描述:https://www.asp.net/media/4071077/aspnet-web-api-poster.pdf

请求消息通过 3 层:宿主(Hosting)、消息处理器(MessageHandler)和控制器(Controller)。它从 HttpServer(宿主层)通过一系列 DelegatingHandlers(消息处理器层)传递到带有过滤器集的 Controller 层。这里有两点对我们很有帮助:DelegatingHandler 和 Filters。我们可以为这两者实现自定义类并将其注册到管道中。

Web Api 中有 4 种特定类型的过滤器:身份验证(Authentication)、授权(Authorization)、异常(Exception)和操作(Action)过滤器。让我们实现一个派生自 ExceptionFilterAttribute 的异常过滤器,它是 Web Api 的一个抽象基类。它实现了 IExceptionFilter,如果发生异常,Web Api 会将消息传递给它。它会调用 OnException(),我们可以将其视为此异常:例如,创建一个异常日志记录器。为了在我们轮询周期内跟踪异常数量的目的,我们只需增加性能计数器的值。

public class ExceptionCounterFilter : ExceptionFilterAttribute
{
    ...
    public ExceptionCounterFilter(PerfMonitor perfMonitor)
    {
        PerfMonitor = perfMonitor;
    }
    ...
    public override void OnException(HttpActionExecutedContext actionExecutedContext)
    {
        PerfMonitor.ExceptionsNum.Increment();
    }
}

它必须是线程安全的。.Net 的 Interlocked 类的静态方法用于此目的,正如您在 Perfon.Core/PerfCounters/PerformanceCounterBase.cs 文件中看到的。与其他同步对象相比,Interlocked 例程非常快速,并具有硬件支持。

现在,我们需要在我们应用程序的 HttpConfiguration 对象中注册自定义过滤器。这里我们将 Perfon 引擎传递到构造函数中。

httpConfiguration.Filters.Add(new ExceptionCounterFilter(this.PerfMonitorBase));

现在让我们看看自定义 DelegatingHandler 的实现。

public class RequestPerfMonitorMessageHandler : DelegatingHandler
{
    private PerfMonitor PerfMonitor {get;set;}

    public RequestPerfMonitorMessageHandler(PerfMonitor perfMonitor)
    {
        PerfMonitor = perfMonitor;
    }

    protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        var st = Stopwatch.StartNew();

        // 1. Track number of requests
        PerfMonitor.RequestNum.Increment();

        // Pass a request through pipeline
        var res = await base.SendAsync(request, cancellationToken);

        // 2. Calculate request length
        long lenReq = 0;
        if (request.Content != null)
        {
            if (request.Content.Headers.ContentLength.HasValue)
            {
                lenReq = request.Content.Headers.ContentLength.Value;
            }
            lenReq += request.Content.Headers.ToString().Length;
        }
        lenReq += request.RequestUri.OriginalString.Length;
        lenReq += request.Headers.ToString().Length;
        PerfMonitor.BytesTrasmittedReq.Add(lenReq);

        // 3. Calculate response length
        long lenResp = 0;
        if (res.Content != null)
        {
            await res.Content.LoadIntoBufferAsync();
            if (res.Content.Headers.ContentLength.HasValue)
            {
                lenResp = res.Content.Headers.ContentLength.Value;
            }
            lenResp += res.Content.Headers.ToString().Length;
        }
        lenResp += res.Headers.ToString().Length;
        PerfMonitor.BytesTrasmittedResp.Add(lenResp);

        st.Stop();

        // 4. Calculate processing time for this request
        PerfMonitor.RequestProcessTime.Add(st.ElapsedMilliseconds);
        PerfMonitor.RequestMaxProcessTime.Add(st.ElapsedMilliseconds);

        // 5. Track number of bad status codes
        if (!res.IsSuccessStatusCode)
        {
            PerfMonitor.BadStatusNum.Increment();
        }

        return res;
    }
}

Web Api 管道中的每个消息都会经过我们 RequestPerfMonitorMessageHandler 的 SendAsync 方法。在这里,我们可以处理 Request 和 Response 属性并获取它们的长度 - 请参阅上面的代码注释。请注意,在计算响应长度之前,我们应该调用 LoadIntoBufferAsync()

不幸的是,我们无法精确计算响应长度,因为在响应离开我们的应用程序后,无法访问 IIS 附加的标头。但这可以忽略不计,尤其是在响应具有很大的正文时。

自定义处理程序应在 HttpConfiguration 类的专用集合中注册。

httpConfiguration.MessageHandlers.Add(new RequestPerfMonitorMessageHandler(this.PerfMonitorBase));

自定义过滤器和 DelegatingHandlers 是 Web Api 架构非常有用的模块,可用于日志记录、时间跟踪、消息的预处理和后处理,例如添加自定义标头。例如,可以测量到的请求处理时间作为自定义标头附加在这里。

res.Headers.Add("X-Perf-ProcessingTime", st.ElapsedMilliseconds.ToString());

收集 MVC5 性能指标

这是 MVC 管道的概述(摘自一篇详细介绍 MVC 管道的文章:https://codeproject.org.cn/articles/1028156/a-detailed-walkthrough-of-asp-net-mvc-request-life

对我们来说,最有趣的是 Action filter。实际上,我们可以为性能跟踪目的实现 IHttpModule,我认为这是一个更好的方法,但这需要用户在 web.config 文件中注册我们的自定义模块。所以,让我们实现一个自定义 Filter,因为我们可以将其注册到库中,而无需强制用户执行其他操作。

一个用于跟踪异常的过滤器,只实现一个方法。

public class ExceptionCounterFilter : FilterAttribute, IExceptionFilter
{
    private PerfMonitor PerfMonitor {get;set;}

    public ExceptionCounterFilter(PerfMonitor perfMonitor)
    {
        PerfMonitor = perfMonitor;
    }

    public void OnException(ExceptionContext exceptionContext)
    {
        PerfMonitor.ExceptionsNum.Increment();
    }
}

现在注册过滤器:

GlobalFilters.Filters.Add(new ExceptionCounterFilter(PerfMonitorBase));

这里我们可以看到 MVC 使用全局静态对象来收集过滤器,这与 Web Api 不同,Web Api 可以使用 HttpConfiguration 类的对象。

一个用于跟踪所有其他计数器的过滤器。

public class PerfMonitoringFilter : ActionFilterAttribute
{
    ...

    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        // We need it to track response length
        filterContext.HttpContext.Response.Filter = new ResponseLengthCalculatingStream(filterContext.HttpContext.Response.Filter, PerfMonitor);

        var request = filterContext.HttpContext.Request;

        var st = Stopwatch.StartNew();

        // 1. Track number of requests
        PerfMonitor.RequestNum.Increment();

        base.OnActionExecuting(filterContext);

        // Keep info about processing start time for this request
        filterContext.HttpContext.Items["stopwatch"] = st;

        long lenReq = 0;
        lenReq += request.TotalBytes;
        lenReq += request.RawUrl.Length;
        lenReq += request.Headers.ToString().Length;
        // 2. Track request length
        PerfMonitor.BytesTrasmittedReq.Add(lenReq);
    }

    public override void OnResultExecuted(ResultExecutedContext filterContext)
    {
        base.OnResultExecuted(filterContext);

        var res = filterContext.HttpContext.Response;

        long lenResp = 0;
        lenResp += res.Headers.ToString().Length;
        // 3. Store response length
        PerfMonitor.BytesTrasmittedResp.Add(lenResp);

        // 4. Track number of bad response status codes
        if (res.StatusCode < 200 || res.StatusCode > 202)
        {
            PerfMonitor.BadStatusNum.Increment();
        }

        var st = filterContext.HttpContext.Items["stopwatch"] as Stopwatch;
        st.Stop();

        // 5. Track time of request processing 
        PerfMonitor.RequestProcessTime.Add(st.ElapsedMilliseconds);
        PerfMonitor.RequestMaxProcessTime.Add(st.ElapsedMilliseconds);
    }
}

这与 Web Api 过滤器略有不同。它有两个方法:OnActionExecutingOnResultExecuted,一个在控制器操作处理请求之前调用,另一个在消息处理之后调用。

注册它:

GlobalFilters.Filters.Add(new PerfMonitoringFilter(PerfMonitorBase));

注意为 Response 过滤器设置的 ResponseLengthCalculatingStream 装饰器。它的目的是拦截响应正文到流的序列化,从而跟踪正文大小。您可以在 Perfon.Mvc/Filters/ResponseLengthCalculatingStream.cs 中看到它。

在路由集合中注册库特定的路由。由于它非常特定,因此应将其放在路由集合的开头。

var r = routes.MapRoute(
    name: "PerfMonitor",
    url: "api/perfcounters/",
    defaults: new { controller = "PerfCounters", action = "Get" }
);
routes.Remove(r);
routes.Insert(0, r);

使用代码

Perfon.Net 的主要思想是轻松地为您的 Web 应用程序添加性能监视能力。Perfon.Net 自动化性能计数器数据的收集、存储、检索和可视化。它内置了 REST API 接口,用于获取计数器值或获取带有 UI 仪表板的 HTML 页面(正是这个 http://perfon.1gb.ru/api/perfcountersui),其中包含性能计数器图表。它将计数器值保存在内存缓存中,或者可以将其存储在嵌入的单文件数据库 LiteDB 中 https://github.com/mbdavid/LiteDB。此外,还提供用于将性能计数器数据存储在 MySql 或 PostgreSql 中的插件。

项目结构

  • Perfon.Interfaces - 框架接口的定义。一个独立的项目,用于在存储驱动程序、性能指示器、通知的自定义实现中引用,或用于 WebApi 2 和 MVC5 以外的自定义框架。
  • Perfon.Core - Perfon.Net 的核心引擎。它包含不同类型计数器的基本实现。该项目实现了几个负责一般 .Net 统计信息、配置和轮询性能指标功能的计数器。它内置了 3 种存储类型:内存缓存、LiteDb(基于文件的嵌入式数据库)和 CSV 文件。此外,还实现了 HTML UI 仪表板。
  • Perfon.WebApi - Perfon.Core 的一个便捷包装器,用于在 Web Api 2 应用程序中使用。它实现了几个负责通过自定义 MessageHandlers 和 Filters 处理请求的性能计数器。此外,它还包含用于获取性能数据和仪表板 UI 的 REST API 控制器。
  • Perfon.Mvc - Perfon.Core 的一个便捷包装器,用于在 Asp.Net MVC5 应用程序中使用。它实现了几个负责通过自定义 Filters 处理请求的性能计数器。此外,它还包含用于获取性能数据和仪表板 UI 的 REST API 控制器。
  • TestServer - 一个使用 Perfon.WebApi 的 Web Api 2 应用程序示例。您可以对项目的 TestController REST API 运行 JMeter 压力测试。该项目正在 http://perfon.1gb.ru/ 上运行。
  • TestMvcApp - 一个使用 Perfon.Mvc 的 Asp.Net MVC 5 应用程序示例。
  • Perfon.StorageDrivers\Perfon.Storage.MySql - 此驱动程序允许从 MySql 服务器存储和检索性能计数器数据。
  • Perfon.StorageDrivers\Perfon.Storage.PostgreSql - 此驱动程序允许从 PosgreSql 服务器存储和检索性能计数器数据。

如何在 Web APi 2 应用程序中使用该库

在 Nuget 包管理器中安装 Perfon.WebApi nuget 包(https://nuget.net.cn/packages/Perfon.WebApi),或者从 github 存储库获取源代码并添加对 Perfon.WebApi 项目的引用。

GlobalConfiguration.Configure(WebApiConfig.Register); //your Web App initialization code. Init PerfMonitor after GlobalConfiguration.Configure

// Create Perfon engine
PerfMonitor = new PerfMonitorForWebApi();

//Configure storage types:
//PerfMonitor.RegisterCSVFileStorage(AppDomain.CurrentDomain.BaseDirectory); -> use it if you want to save counters to CSV file
//PerfMonitor.RegisterInMemoryCacheStorage(60*60*1); -> use it if you want to save counters in memory wih expiration 1 hour = 60*60 sec
//PerfMonitor.RegisterStorages( new Perfon.Storage.PostgreSql.PerfCounterPostgreSqlStorage(@"host=xxx;port=xxx;Database=db_name;username=user_name;password=pswd")) // For use PostgreSql as a Storage
//PerfMonitor.RegisterStorages( new Perfon.Storage.MySql.PerfCounterMySqlStorage(@"mysql_connection_string")) // For use MySql as a Storage 
PerfMonitor.RegisterLiteDbStorage(AppDomain.CurrentDomain.BaseDirectory+"\\path_to_db"); //use it for storing performance counters data to LiteDB file 

PerfMonitor.OnError += (a, errArg) => Console.WriteLine("PerfLibForWebApi:"+errArg.Message); // NOT mandatory: if you need to get error reports from the library

//NOT mandatory: Change some default settings if needed
PerfMonitor.Configuration.DoNotStorePerfCountersIfReqLessOrEqThan = 0; //Do not store perf values if RequestsNum = 0 during poll period
PerfMonitor.Configuration.EnablePerfApi = true; // Enable getting perf values by API GET addresses 'api/perfcounters' and  'api/perfcounters?name={name}'. Disabled by default
PerfMonitor.Configuration.EnablePerfUIApi = true; // Enable getting UI html page with perf counters values by API GET 'api/perfcountersui' or 'api/perfcountersuipanel'. Disabled by default

//Start the poll of performance counter values with period 10 sec
PerfMonitor.Start(GlobalConfiguration.Configuration, 10);

请注意,您需要在 Web Api 应用程序的 WebApiConfig.Register() 中通过 config.MapHttpAttributeRoutes() 启用属性路由,以便通过 REST API 检索性能值并使用仪表板 UI API,因为 Perfon 控制器使用属性路由。

使用 URL api/perfcountersui 获取 UI 仪表板。

在 Application_End 中应停止引擎。

PerfMonitor.Stop();

要使用 PerfCounterPostgreSqlStorage 或 PerfCounterMySqlStorage,您需要添加相应的项目引用或安装相应的 nuget 包 https://nuget.net.cn/packages/Perfon.Storage.PostgreSqlhttps://nuget.net.cn/packages/Perfon.Storage.MySql

不仅可以通过 REST API,还可以在代码中通过以下方式获取计数器值:

PerfMonitor.QueryCounterValues(...)

UI 仪表板也以 HTML 字符串的形式在代码中可用。

PerfMonitor.UIPage

PerfMonitor.UIPanel

提供了一个完整的示例 - TestServer 项目。它可以在 IIS 中启动,并且 UI 仪表板将在 URL api/perfcountersui 上可用。它正在 http://perfon.1gb.ru/ 上进行演示。

 

可以改进的地方

Web Api 包装器需要启用属性路由。不幸的是,没有简单的方法(就像 MVC 包装器中那样)将 Perfon.Net 特定的路由添加到路由集合的开头。这可以通过替换 Controller Dispatcher 和 Routing Dispatcher Handlers 来完成,但这对于如此触及客户端应用程序配置来说并不是很好。

 

© . All rights reserved.