通常我们需要监测ASP.NET MVC 或 Web API 的应用程序的性能时,通常采用的是自定义性能计数器,性能计数器会引发无休止的运维问题(损坏的计数器、权限问题等)。这篇文章向你介绍一个新的替代性能计数器的工具Metrics.NET,因为是它是内部的,所以我们能够向系统中添加更多更有意义的度量标准。

Metrics.NET(https://github.com/etishor/Metrics.NET)是一个给CLR 提供度量工具的包,它是移植自Java的metrics,支持的平台 .NET 4.5.1, .NET 4.5, .NET 4.0 和 Mono 3.8.0,在c#代码中嵌入Metrics代码,可以方便的对业务代码的各个指标进行监控, 提供5种度量的类型:Gauges, Counters, Histograms, Meters,Timers:

Gauges

Gauge是最简单的度量类型,只有一个简单的返回值,例如,你的应用中有一个由第三方类库中保持的一个度量值,你可以很容易的通过Gauge来度量他

        long milliseconds = this.ConvertTicksToMilliseconds(elapsedTicks);
String controllerName = this.actionInfo.ControllerName;
String actionName = this.actionInfo.ActionName;
string counterName = string.Format("{0} {1} {2}", controllerName, actionName, COUNTER_NAME);
Metric.Context(this.actionInfo.ActionType).Gauge(counterName, () => milliseconds, Unit.Custom("Milliseconds"));

那么Metrics会创建一个叫做[MVC] Account LogOn Last Call Elapsed Time.Gauge的Gauge,返回最新的一个请求的时间。

Counters

Counter是一个简单64位的计数器:

        String categoryName = this.actionInfo.ControllerName;
String instanceName = this.actionInfo.ActionName;
string counterName = string.Format("{0} {1} {2}", categoryName, instanceName, COUNTER_NAME);
this.callsInProgressCounter = Metric.Context(this.actionInfo.ActionType).Counter(counterName, Unit.Custom(COUNTER_NAME)); /// <summary>
/// Constant defining the name of this counter
/// </summary>
public const String COUNTER_NAME = "ActiveRequests"; private Counter callsInProgressCounter; /// <summary>
/// Method called by the custom action filter just prior to the action begining to execute
/// </summary>
/// <remarks>
/// This method increments the Calls in Progress counter by 1
/// </remarks>
public override void OnActionStart()
{
this.callsInProgressCounter.Increment();
} /// <summary>
/// Method called by the custom action filter after the action completes
/// </summary>
/// <remarks>
/// This method decrements the Calls in Progress counter by 1
/// </remarks>
public override void OnActionComplete(long elapsedTicks, bool exceptionThrown)
{
this.callsInProgressCounter.Decrement();
}
所有的Counter都是从0开始,上述代码描述的当前的请求数。

Histograms-直方图

Histrogram是用来度量流数据中Value的分布情况,例如,每一个POST/PUT请求中的内容大小:

        public PostAndPutRequestSizeMetric(ActionInfo info)
: base(info)
{
this.histogram = Metric.Context(this.actionInfo.ActionType).Histogram(COUNTER_NAME, Unit.Bytes, SamplingType.FavourRecent);
} /// <summary>
/// Constant defining the name of this counter
/// </summary>
public const String COUNTER_NAME = "Post & Put Request Size"; /// <summary>
/// Reference to the performance counter
/// </summary>
private Histogram histogram; public override void OnActionStart()
{
var method = this.actionInfo.HttpMethod.ToUpper();
if (method == "POST" || method == "PUT")
{
histogram.Update(this.actionInfo.ContentLength);
}
}

Histrogram 的度量值不仅仅是计算最大/小值、平均值,方差,他还展现了分位数(如中位数,或者95th分位数),如75%,90%,98%,99%的数据在哪个范围内。

传统上,中位数(或者其他分位数)是在一个完整的数据集中进行计算的,通过对数据的排序,然后取出中间值(或者离结束1%的那个数字,来计算99th分位数)。这种做法是在小数据集,或者是批量计算的系统中,但是在一个高吞吐、低延时的系统中是不合适的。

一个解决方案就是从数据中进行抽样,保存一个少量、易管理的数据集,并且能够反应总体数据流的统计信息。使我们能够简单快速的计算给定分位数的近似值。这种技术称作reservoir sampling。

Metrics中提供两种类型的直方图:uniform跟biased。

Uniform Histograms

Uniform Histogram提供直方图完整的生命周期内的有效的中位数,它会返回一个中位值。例如:这个中位数是对所有值的直方图进行了更新,它使用了一种叫做Vitter’s R的算法,随机选择了一些线性递增的样本。

当你需要长期的测量,请使用Uniform Histograms。在你想要知道流数据的分布中是否最近变化的话,那么不要使用这种。

Biased Histograms

Biased Histogram提供代表最近5分钟数据的分位数,他使用了一种forward-decayingpriority sample的算法,这个算法通过对最新的数据进行指数加权,不同于Uniform算法,Biased Histogram体现的是最新的数据,可以让你快速的指导最新的数据分布发生了什么变化。Timers中使用了Biased Histogram。

Meters

Meter度量一系列事件发生的比率:


 public DeltaExceptionsThrownMetric(ActionInfo info)
: base(info)
{
this.deltaExceptionsThrownCounter
= Metric.Context(this.actionInfo.ActionType).Meter(COUNTER_NAME, Unit.Errors, TimeUnit.Seconds);
} /// <summary>
/// Constant defining the name of this counter
/// </summary>
public const String COUNTER_NAME = "Errors"; /// <summary>
/// Reference to the performance counter
/// </summary>
private Meter deltaExceptionsThrownCounter; /// <summary>
/// Method called by the custom action filter after the action completes
/// </summary>
/// <remarks>
/// If exceptionThrown is true, then the Total Exceptions Thrown counter will be
/// incremented by 1
/// </remarks>
public override void OnActionComplete(long elapsedTicks, bool exceptionThrown)
{
if (exceptionThrown)
this.deltaExceptionsThrownCounter.Mark();
}
 

Meter需要除了Name之外的两个额外的信息,事件类型(enent type)跟比率单位(rate unit)。事件类型简单的描述Meter需要度量的事件类型,在上面的例子中,Meter是度量失败的请求数,所以他的事件类型也叫做“Errors”。比率单位是命名这个比率的单位时间,在上面的例子中,这个Meter是度量每秒钟的失败请求次数,所以他的单位就是秒。这两个参数加起来就是表述这个Meter,描述每秒钟的失败请求数。

Meter从几个角度上度量事件的比率,平均值是时间的平均比率,它描述的是整个应用完整的生命周期的情况(例如,所有的处理的请求数除以运行的秒数),它并不描述最新的数据。幸好,Meters中还有其他3个不同的指数方式表现的平均值,1分钟,5分钟,15分钟内的滑动平均值。

Hint:这个平均值跟Unix中的uptime跟top中秒数的Load的含义是一致的。

Timers

Timer是Histogram跟Meter的一个组合

 public TimerForEachRequestMetric(ActionInfo info)
: base(info)
{
String controllerName = this.actionInfo.ControllerName;
String actionName = this.actionInfo.ActionName;
string counterName = string.Format("{0}{1}", controllerName, actionName); this.averageTimeCounter = Metric.Context(this.actionInfo.ActionType).Timer(counterName, Unit.Requests, SamplingType.FavourRecent,
TimeUnit.Seconds, TimeUnit.Milliseconds);
} #region Member Variables
private Timer averageTimeCounter;
#endregion /// <summary>
/// Method called by the custom action filter after the action completes
/// </summary>
/// <remarks>
/// This method increments the Average Time per Call counter by the number of ticks
/// the action took to complete and the base counter is incremented by 1 (this is
/// done in the PerfCounterUtil.IncrementTimer() method).
/// </remarks>
/// <param name="elapsedTicks">A long of the number of ticks it took to complete the action</param>
public override void OnActionComplete(long elapsedTicks, bool exceptionThrown)
{
averageTimeCounter.Record(elapsedTicks, TimeUnit.Nanoseconds);
}

Timer需要的参数处理Name之外还需要,持续时间单位跟比率时间单位,持续时间单位是要度量的时间的期间的一个单位,在上面的例子中,就是MILLISECONDS,表示这段周期内的数据会按照毫秒来进行度量。比率时间单位跟Meters的一致。

Health Checks(健康检查)

Meters提供一种一致的、统一的方法来对应用进行健康检查,健康检查是一个基础的对应用是否正常运行的自我检查。

Reporters报告

Reporters是将你的应用中所有的度量指标展现出来的一种方式,metrics.net中用了三种方法来导出你的度量指标,Http,Console跟CSV文件, Reporters是可定制的。例如可以使用Log4net进行输出,具体参见 https://github.com/nkot/Metrics.Log4Net

  Metric.Config.WithHttpEndpoint("http://localhost:1234/")
.WithAllCounters()
.WithReporting(config => config.WithCSVReports(@"c:\temp\csv", TimeSpan.FromSeconds(10))
.WithTextFileReport(@"C:\temp\reports\metrics.txt", TimeSpan.FromSeconds(10)));
 
    
 上面我们介绍了基于Metrics.NET构建的ASP.NET MVC 应用程序的性能指标,如下表所示:
计数器名称 描述
Last Call Elapsed Time 已完成最后一次调用的所花费的时间。这是表示所有已完成请求的时间测量中的最新一个点。它不是平均值。
Request Timer 统计执行时间以及其分布情况
POST & PUT Request Size histogram
POST/PUT请求中的内容大小
Global Error Meter ASP.NET引发 未捕获的异常的比率。如果此计数器增加时,它会显示与该应用程序的健康问题
Delta Calls
最后一个采样周期内被调用的次数
ActiveRequests
当前的并发请求数
 

通过自定义Action Filter集成到ASP.NET MVC

定义一个MvcPerformanceAttribute,继承自ActionFilterAttribute:

/// <summary>
/// Custom action filter to track the performance of MVC actions
/// </summary>
public class MvcPerformanceAttribute : ActionFilterAttribute
{ public MvcPerformanceAttribute()
{
} /// <summary>
/// Constant to identify MVC Action Types (used in the instance name)
/// </summary>
public const String ACTION_TYPE = "MVC"; /// <summary>
/// Method called before the action method starts processing
/// </summary>
/// <param name="filterContext">An ActionExecutingContext object</param>
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
// First thing is to check if performance is enabled globally. If not, return
if ( ConfigInfo.Value.PerformanceEnabled == false)
{
return;
} // Second thing, check if performance tracking has been turned off for this action
// If the DoNotTrackAttribute is present, then return
ActionDescriptor actionDescriptor = filterContext.ActionDescriptor; if (actionDescriptor.GetCustomAttributes(typeof(DoNotTrackPerformanceAttribute), true).Length > 0
|| actionDescriptor.ControllerDescriptor.GetCustomAttributes(typeof(DoNotTrackPerformanceAttribute), true).Length > 0)
{
return;
} // ActionInfo encapsulates all the info about the action being invoked
ActionInfo info = this.CreateActionInfo(filterContext); // PerformanceTracker is the object that tracks performance and is attached to the request
PerformanceTracker tracker = new PerformanceTracker(info); // Store this on the request
String contextKey = this.GetUniqueContextKey(filterContext.ActionDescriptor.UniqueId);
HttpContext.Current.Items.Add(contextKey, tracker); // Process the action start - this is what starts the timer and increments any
// required counters before the action executes
tracker.ProcessActionStart();
} /// <summary>
/// Method called after the action method has completed executing
/// </summary>
/// <remarks>
/// This method first checks to make sure we are indeed tracking performance. If so, it stops
/// the stopwatch and then calls the OnActionComplete() method of all of the performance metric
/// objects attached to this action filter
/// </remarks>
/// <param name="filterContext">An ActionExecutedConext object</param>
public override void OnActionExecuted(ActionExecutedContext filterContext)
{
// This is the unique key the PerformanceTracker object would be stored under
String contextKey = this.GetUniqueContextKey(filterContext.ActionDescriptor.UniqueId); // Check if there is an object on the request. If not, must not be tracking performance
// for this action, so just go ahead and return
if (HttpContext.Current.Items.Contains(contextKey) == false)
{
return;
} // If we are here, we are tracking performance. Extract the object from the request and call
// ProcessActionComplete. This will stop the stopwatch and update the performance metrics
PerformanceTracker tracker = HttpContext.Current.Items[contextKey] as PerformanceTracker; if (tracker != null)
{
bool exceptionThrown = (filterContext.Exception != null);
tracker.ProcessActionComplete(exceptionThrown);
}
} #region Helper Methdos /// <summary>
/// Helper method to create the ActionInfo object containing the info about the action that is getting called
/// </summary>
/// <param name="actionContext">The ActionExecutingContext from the OnActionExecuting() method</param>
/// <returns>An ActionInfo object that contains all the information pertaining to what action is being executed</returns>
private ActionInfo CreateActionInfo(ActionExecutingContext actionContext)
{
var parameters = actionContext.ActionDescriptor.GetParameters().Select(p => p.ParameterName);
String parameterString = String.Join(",", parameters); int processId = ConfigInfo.Value.ProcessId;
String categoryName = ConfigInfo.Value.PerformanceCategoryName;
String controllerName = actionContext.ActionDescriptor.ControllerDescriptor.ControllerName;
String actionName = actionContext.ActionDescriptor.ActionName;
String httpMethod = HttpContext.Current.Request.HttpMethod;
int contentLength = HttpContext.Current.Request.ContentLength; ActionInfo info = new ActionInfo(processId, categoryName, ACTION_TYPE,
controllerName, actionName, httpMethod, parameterString,contentLength); return info;
} /// <summary>
/// Helper method to form the key that will be used to store/retrieve the PerformanceTracker object
/// off if the HttpContext
/// </summary>
/// <remarks>
/// To minimize any chance of collisions, this method concatenates the full name of this class
/// with the UniqueID of the MVC action to get a unique key to use
/// </remarks>
/// <param name="actionUniqueId">A String of the unique id assigned by ASP.NET to the MVC action</param>
/// <returns>A Strin suitable to be used for the key</returns>
private String GetUniqueContextKey(String actionUniqueId)
{
return this.GetType().FullName + ":" + actionUniqueId;
} #endregion
}

首要任务是确定是否正在跟踪此控制器操作性能。首先,它会检查一个名为 ConfigInfo,看看是否在整个应用程序范围的基础上启用性能的单例类。如果 ConfigInfo 类不是能够在 Web.Config 文件中查找的AspNetPerformance.EnablePerformanceMonitoring,此调用将返回 false。

然后应该跟踪此控制器操作性能。辅助方法用于创建一个 ActionInfo 对象,它是一个对象,封装有关控制器操作的所有信息。然后创建 PerformanceTracker 对象,它是具有主要负责跟踪性能的控制器操作的对象。度量性能的每个请求将相关联的 PerformanceTracker 对象和关联的 PerformanceTracker 对象将需要再次检索在 OnActionExecuted() 方法中控制器动作完成后。PerformanceTracker 对象存储在当前的 HttpContext 对象项目字典中。对 HttpContext 项目字典是用于当数据需要在请求过程中不同的 Http 处理程序和模块之间共享而设计的。使用的诀窍是基于属性类型的完整名称和 ASP.NET 生成的唯一 id 的方法。通过将这些因素结合在一起,我们应该与其他模块的使用项目字典任何关键碰撞安全。最后,调用 PerformanceTracker 对象的 ProcessActionStart() 方法。

 internal void ProcessActionStart()
{
try
{
// Use the factory class to get all of the performance metrics that are being tracked
// for MVC Actions
this.performanceMetrics = PerformanceMetricFactory.GetPerformanceMetrics(actionInfo); // Iterate through each metric and call the OnActionStart() method
// Start off a task to do this so it can it does not block and minimized impact to the user
Task t = Task.Factory.StartNew(() =>
{
foreach (PerformanceMetricBase m in this.performanceMetrics)
{
m.OnActionStart();
}
}); this.stopwatch = Stopwatch.StartNew();
}
catch (Exception ex)
{
String message = String.Format("Exception {0} occurred PerformanceTracker.ProcessActionStart(). Message {1}\nStackTrace {0}",
ex.GetType().FullName, ex.Message, ex.StackTrace);
Trace.WriteLine(message);
}
}

PerformanceMetricBase 对象和 PerformanceMetricFactory

更新实际性能计数器的任务是继承 PerformanceMetricBase 类的对象。这些对象作为PerformanceTracker 对象的中间人 ,并需要更新的任何性能计数器。代码分解为单独的一组对象允许要专注于管理全过程的测量性能的控制器操作和离开如何更新计数器对 PerformanceMetricBase 对象的详细信息的 PerformanceTracker 对象。如果我们想要添加额外的性能指标,可以通过简单地编写一个新的类,扩展了 PerformanceMetricBase 并不会受到 PerformanceTracker 的代码的干扰。

每个子类扩展 PerformanceMetricBase 负责更新对应的值到这篇文章前面定义的自定义性能计数器之一。因此,每个类将包含持有对 Metric.NET 的引用对象,他们是负责更新的成员变量。通常,这是一个单一的Metric.NET 对象。

PerformanceMetricBase 提供了两种虚拟方法,OnActionStart() 和 OnActionComplete() 子类别在哪里能够对性能计数器执行更新。需要覆盖至少一个方法实现或可重写这两种方法。 具体的实现代码放在Github: https://github.com/geffzhang/AspNetPerformanceMetrics

参考文章

Building Performance Metrics into ASP.NET MVC Applications

http://www.cnblogs.com/yangecnu/p/Using-Metrics-to-Profiling-WebService-Performance.html

使用Metrics.NET 构建 ASP.NET MVC 应用程序的性能指标的更多相关文章

  1. NHibernate构建一个ASP.NET MVC应用程序

    NHibernate构建一个ASP.NET MVC应用程序 什么是Nhibernate? NHibernate是一个面向.NET环境的对象/关系数据库映射工具.对象/关系数据库映射(object/re ...

  2. 测试驱动 ASP.NET MVC 和构建可测试 ASP.NET MVC 应用程序

    [测试驱动 ASP.NET MVC] http://t.cn/8kdi4Wl [构建可测试 ASP.NET MVC 应用程序]http://t.cn/8kdi4Wj

  3. 使用Razor Generator构建模块化ASP.NET MVC应用程序

    在构建Web应用程序的时候,我们很难做到模块化的开发,这是因为Web应用程序不仅仅包含编译的C#代码,还包含了js.css和aspx等资源. 在ASP.NET MVC中,我们发布应用程序的时候,还会包 ...

  4. [渣译文] 使用 MVC 5 的 EF6 Code First 入门 系列:为ASP.NET MVC应用程序更新相关数据

    这是微软官方教程Getting Started with Entity Framework 6 Code First using MVC 5 系列的翻译,这里是第八篇:为ASP.NET MVC应用程序 ...

  5. ASP.NET MVC应用程序执行过程分析

    ASP.NET MVC应用程序执行过程分析 2009-08-14 17:57 朱先忠 朱先忠的博客 字号:T | T   ASP.NET MVC框架提供了支持Visual Studio的工程模板.本文 ...

  6. ASP.NET MVC应用程序更新相关数据

    为ASP.NET MVC应用程序更新相关数据 这是微软官方教程Getting Started with Entity Framework 6 Code First using MVC 5 系列的翻译, ...

  7. 04 入门 - ASP.NET MVC应用程序的结构

    目录索引:<ASP.NET MVC 5 高级编程>学习笔记 用Visual Studio创建了一个新的ASP.NET MVC应用程序后,将自动向这个项目中添加一些文件和目录. 如图所示: ...

  8. asp.net MVC 应用程序的生命周期

    下面这篇文章总结了 asp.net MVC 框架程序的生命周期.觉得写得不错,故转载一下. 转载自:http://www.cnblogs.com/yplong/p/5582576.html       ...

  9. 学习ASP.NET MVC(一)——我的第一个ASP.NET MVC应用程序

    学习ASP.NET MVC系列: 学习ASP.NET MVC(一)——我的第一个ASP.NET MVC应用程序 学习ASP.NET MVC(二)——我的第一个ASP.NET MVC 控制器 学习ASP ...

随机推荐

  1. 异步任务队列Celery在Django中的使用

    前段时间在Django Web平台开发中,碰到一些请求执行的任务时间较长(几分钟),为了加快用户的响应时间,因此决定采用异步任务的方式在后台执行这些任务.在同事的指引下接触了Celery这个异步任务队 ...

  2. 恢复SQL Server被误删除的数据

    恢复SQL Server被误删除的数据 <恢复SQL Server被误删除的数据(再扩展)> 地址:http://www.cnblogs.com/lyhabc/p/4620764.html ...

  3. 【SQLServer】记一次数据迁移-标识重复的简单处理

    汇总篇:http://www.cnblogs.com/dunitian/p/4822808.html#tsql 今天在数据迁移的时候因为手贱遇到一个坑爹问题,发来大家乐乐,也传授新手点经验 迁移惯用就 ...

  4. DataTable 转换成 Json的3种方法

    在web开发中,我们可能会有这样的需求,为了便于前台的JS的处理,我们需要将查询出的数据源格式比如:List<T>.DataTable转换为Json格式.特别在使用Extjs框架的时候,A ...

  5. sql的那些事(一)

    一.概述 书写sql是我们程序猿在开发中必不可少的技能,优秀的sql语句,执行起来吊炸天,性能杠杠的.差劲的sql,不仅使查询效率降低,维护起来也十分不便.一切都是为了性能,一切都是为了业务,你觉得你 ...

  6. Spark读写Hbase的二种方式对比

    作者:Syn良子 出处:http://www.cnblogs.com/cssdongl 转载请注明出处 一.传统方式 这种方式就是常用的TableInputFormat和TableOutputForm ...

  7. Windows下Visual studio 2013 编译 Audacity

    编译的Audacity版本为2.1.2,由于实在windows下编译,其源代码可以从Github上取得 git clone https://github.com/audacity/audacity. ...

  8. javaScript中的小细节-script标签中的预解析

    首先介绍预解析,虽然预解析字面意思很好理解,但是却是出坑出的最多的地方,也是bug经常会有的地方,利用好预解析的特性可以解决很多问题,并且提高代码的质量及数量,浏览器在解析代码前会把变量的声明和函数( ...

  9. ReactiveCocoa代码实践之-UI组件的RAC信号操作

    上一节是自己对网络层的一些重构,本节是自己一些代码小实践做出的一些demo程序,基本涵盖大多数UI控件操作. 一.用UISlider实现调色板 假设我们现在做一个demo,上面有一个View用来展示颜 ...

  10. CodingLife主题更新

    收到反馈说CodingLife主题某些地方显示有问题,于是进行了更新,并且已提交.官方那边正在进行测试,我自己这边测完应该是没问题的,但不知道官方啥时候会进行更新,所以把CSS代码贴出来,有需要的可以 ...