文章出處

通常我們需要監測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)));
 
    image
 上面我們介紹了基于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


文章列表




Avast logo

Avast 防毒軟體已檢查此封電子郵件的病毒。
www.avast.com


arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()