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





5.00/5 (14投票s)
用于监控 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 过滤器略有不同。它有两个方法:OnActionExecuting
和 OnResultExecuted
,一个在控制器操作处理请求之前调用,另一个在消息处理之后调用。
注册它:
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.PostgreSql 或 https://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 来完成,但这对于如此触及客户端应用程序配置来说并不是很好。