自製ActionFilter小鋼炮:MVC/WebAPI程式效能計時器

自製ActionFilter小鋼炮:MVC/WebAPI程式效能計時器

炮

最近進行一些ASP.NET MVC/ASP.NET Web API系統效能改善的工作,改善之初當然是找出最耗效能(或是說較耗效能)的程式碼來下手改善。找出吃資源程式碼的方式很多,第三方有許多不錯的選擇,不過我需求真的很簡單,只想得到一個數字,在 .NET Framework 最簡單是用的System.Diagnostics.Stopwatch 類別進行耗用時間的測量。

單一程式段落效能計時器

我們先從最簡單的開始,計算一段程式碼的執行時間。

ASP.NET MVC - Action段落效能計時器

 public ActionResult InActionTimer()                                                              
 {                                                                                                
     System.Diagnostics.Stopwatch stopWatch = new System.Diagnostics.Stopwatch();                 
     stopWatch.Start();                                                                           
     // 模擬程式執行                                                                                
     System.Threading.Thread.Sleep(1527);                                                         
     stopWatch.Stop();                                                                            
     TimeSpan ts = stopWatch.Elapsed;                                                             
                                                                                                  
     System.Diagnostics.Debug.WriteLine($"ActionTimer: {ts.ToString()} from Foo/InActionTimer");  
                                                                                                  
     return Content($"ActionTimer: {ts.ToString()} from Foo/InActionTimer");                      
 }                                                                                                  
 
MVC stopWatch

可以看到.Start().Stop()之間的程碼計算出耗費時間。

ASP.NET Web API - Action段落效能計時器

 public IHttpActionResult GetInActionTimer()                                                       
 {                                                                                                 
     System.Diagnostics.Stopwatch stopWatch = new System.Diagnostics.Stopwatch();                  
     stopWatch.Start();                                                                            
     System.Threading.Thread.Sleep(1527);                                                          
     stopWatch.Stop();                                                                             
     TimeSpan ts = stopWatch.Elapsed;                                                              
                                                                                                   
     System.Diagnostics.Debug.WriteLine($"ActionTimer: {ts.ToString()} from Bar/GetInActionTimer");
                                                                                                   
     return Ok($"ActionTimer: {ts.ToString()} from Bar/GetInActionTimer");                         
 }
 
API stopWatch

Stopwatch 的程式碼幾乎一模一樣。

雖然 Stopwatch 的程式碼非常簡單,但當我們大量使用時就很容易發現一個壞味道。以 ASP.NET MVC 為例:

 public ActionResult InActionTimer1()                                                              
 {                                                                                                
     System.Diagnostics.Stopwatch stopWatch = new System.Diagnostics.Stopwatch();                 
     stopWatch.Start();                                                                           
     // 模擬程式執行                                                                                
     System.Threading.Thread.Sleep(1527);                                                         
     stopWatch.Stop();                                                                            
     TimeSpan ts = stopWatch.Elapsed;                                                             
                                                                                                  
     System.Diagnostics.Debug.WriteLine($"ActionTimer: {ts.ToString()} from Foo/InActionTimer1");  
                                                                                                  
     return Content($"ActionTimer: {ts.ToString()} from Foo/InActionTimer1");                      
 } 

 public ActionResult InActionTimer2()                                                              
 {                                                                                                
     System.Diagnostics.Stopwatch stopWatch = new System.Diagnostics.Stopwatch();                 
     stopWatch.Start();                                                                           
     // 模擬程式執行                                                                                
     System.Threading.Thread.Sleep(1527);                                                         
     stopWatch.Stop();                                                                            
     TimeSpan ts = stopWatch.Elapsed;                                                             
                                                                                                  
     System.Diagnostics.Debug.WriteLine($"ActionTimer: {ts.ToString()} from Foo/InActionTimer2");  
                                                                                                  
     return Content($"ActionTimer: {ts.ToString()} from Foo/InActionTimer2");                      
 }    
 

好呆哦!

小鋼炮ActionFilter上場

ActionFilter作用於MVC/Web API的Action方法之前之後,這種計時Action方法情境最合適的不二人選當然是ActionFilter。

ASP.NET MVC - ActionTimerAttribute

 public class ActionTimerAttribute : ActionFilterAttribute
 {
     private Stopwatch swAction;
     private Stopwatch swView;

     public ActionTimerAttribute()
     {                                                                                                            
         swAction = new Stopwatch();                                                                              
         swView = new Stopwatch();                                                                                
     }                                                                                                            
                                                                                                                  
     #region 計算 Action 執行時間
                                                                                                                  
     public override void OnActionExecuting(ActionExecutingContext filterContext)                                 
     {                                                                                                            
         swAction.Start();                                                                                        
         base.OnActionExecuting(filterContext);                                                                   
     }                                                                                                            
                                                                                                                  
     public override void OnActionExecuted(ActionExecutedContext filterContext)                                   
     {                                                                                                            
         var controllerName = (string)filterContext.RouteData.Values["controller"];                               
         var actionName = (string)filterContext.RouteData.Values["action"];                                       
                                                                                                                  
         swAction.Stop();                                                                                         
         TimeSpan ts = swAction.Elapsed;                                                                          
         swAction.Reset();                                                                                        
         Debug.WriteLine($"ActionTimer: {ts.ToString()} from {controllerName}/{actionName}");                     
                                                                                                                  
         base.OnActionExecuted(filterContext);                                                                    
     }                                                                                                            
     #endregion                                                                                                   
                                                                                                                  
     #region 計算 View 執行時間
                                                                                                                  
     public override void OnResultExecuting(ResultExecutingContext filterContext)                                 
     {                                                                                                            
         swView.Start();                                                                                          
         base.OnResultExecuting(filterContext);                                                                   
     }                                                                                                            
                                                                                                                  
     public override void OnResultExecuted(ResultExecutedContext filterContext)                                   
     {                                                                                                            
         var controllerName = (string)filterContext.RouteData.Values["controller"];                               
         var actionName = (string)filterContext.RouteData.Values["action"];                                       
                                                                                                                  
         swView.Stop();                                                                                           
         TimeSpan ts = swView.Elapsed;                                                                            
         swView.Reset();                                                                                          
         Debug.WriteLine($"ViewTimer: {ts.ToString()} from {controllerName}/{actionName}");                       
         base.OnResultExecuted(filterContext);                                                                    
     }                                                                                                            
                                                                                                                  
     #endregion                                                                                                   
 }                                                                                                                
 

利用ASP.NET MVC的ActionFilterAttributeOnActionExecutingOnActionExecutedOnResultExecutingOnResultExecuted四大天王,我們不只可以計算Action方法的耗費時間,我們連View Engine的耗費時間都能輕鬆計算出。

ASP.NET Web API - ActionTimerAttribute

 public class ActionTimerAttribute : ActionFilterAttribute                                                                 
 {      
     private Stopwatch sw;
     public ActionTimerAttribute()
     {
      sw = new Stopwatch();
     }         
                                                                                                                           
     #region 計算 API Action 執行時間
                                                                                                                           
     public override void OnActionExecuting(HttpActionContext actionContext)
     {   
                sw.Start();         
         base.OnActionExecuting(actionContext);
     }         
                                                                                                                           
     public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
     {         
         string controllerName = actionExecutedContext.ActionContext.ControllerContext.ControllerDescriptor.ControllerName;
         string actionName = actionExecutedContext.ActionContext.ActionDescriptor.ActionName;
                                                                                                                           
         sw.Stop();                                                                                                        
         TimeSpan ts = sw.Elapsed;
         sw.Reset();                                                                                                       
         Debug.WriteLine($"ActionTimer: {ts.ToString()} from {controllerName}/{actionName}");
         base.OnActionExecuted(actionExecutedContext);
     }                                                                                                            

     #endregion
 }  
 

ASP.NET Web API 的 ActionFilter 相對於 ASP.NET MVC 相對於簡單許多。

輸出至client header

耗費時間除了輸出至"輸出視窗"或"日誌系統"外,我們也能將相關資訊輸出至回應訊息的header中。

     public override void OnActionExecuted(ActionExecutedContext filterContext)                                   
     {                                                                                                            
         var controllerName = (string)filterContext.RouteData.Values["controller"];                               
         var actionName = (string)filterContext.RouteData.Values["action"];                                       
                                                                                                                  
         swAction.Stop();                                                                                         
         TimeSpan ts = swAction.Elapsed;                                                                          
         swAction.Reset();                                                                                        
         Debug.WriteLine($"ActionTimer: {ts.ToString()} from {controllerName}/{actionName}");                     

   // 輸出至 client header
         var httpContext = filterContext.HttpContext;
         var response = httpContext.Response;
         response.AddHeader("X-Stopwatch", ts.ToString());                                                                                                                  
         base.OnActionExecuted(filterContext);                                                                    
     }                       
 

接著只需要讓ActionTimerAttribute在有效作用域(Global、Controller、Action)生效,我們自製的小鋼炮就會不斷發射,讓你輕鬆找出關鍵數字。ActionFilter 好威呀。

GitHub取得完整專案:https://github.com/kkbruce/TimingTheMVCandAPIActions

沒有留言:

張貼留言

感謝您的留言,如果我的文章你喜歡或對你有幫助,按個「讚」或「分享」它,我會很高興的。