自製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");
}
可以看到.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");
}
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的ActionFilterAttribute的OnActionExecuting、OnActionExecuted、OnResultExecuting、OnResultExecuted四大天王,我們不只可以計算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);
}



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