在 .NET 應用程式中, 如何來偵測系統的效能 ? 用什麼樣的架構 ? 什麼樣的元件 ?
這個問題其實是很有趣的, 快速整理的一下手邊的經驗與資源. 與各位夥伴分享
----
方法其實很多, 過去有許多很有意思的文章.. 以及範例. 內容都在討論 .NET 應用程式中透過 Performance Counter 來預先做好 效能偵測器 !
做法其實很簡單 ! 透過 .NET Framework 中 System.Diagnostics 元件庫裏頭的相關 Performance Counter, 其實你可以在你的應用程式中 埋下許多重要的 偵測指標.
不過, 如果真要量產在你的軟體架構中, 各位資深的設計夥伴大概就會想到, 這個 API 其實還可以在包裝一下, 成為更好的共用元件與模組 !. 而我本人也相當同意這個想法 ~
兩年前, 為了這個想法, 我也花了不少力氣 整理了一些東西, 其中不少設計, 是來自 偉大的 Open Source 領域, 這裡要特別和各位談一篇文章, 因為我的版本後來就是由這裏開始改起~~~
Using custom attributes to add performance counters to your application By Duncan Edwards Jones.
Duncan 的 VB.NET 1.1 範例元件中, 導入了 Reflection 的概念, 這和我的想法不謀而合~. 只是 他的設計方法 到最後 和我的需求, 卻是有點差異的 ~
我的目標是 讓我的 開發團隊, 在我的架構下, 可以很簡單的寫程式. 完全不用考慮到 要寫死一堆為了做好 Performance Counter 的程式碼. 簡單到什麼程度呢 ? 在當時, 我想到一個好方法, 透過 .NET 1.1 最酷的 Attribute ��籤. 以及 動態的 Reflection 機制 ! 如下範例 :
[PerformanceCounterCategory("MyBusinessApplication", "My Business Application Performance Counter Category")] public class SomeBusinessSerivceProvider { [PerformanceCounter("HowFastWeMakeTheMoney", "Count how long we fill an big order", PerformanceCounterType.RateOfCountsPerSecond32, PerformanceCounterAttribute.PerformanceCounterUpdateMethod.Update_Increment)] [PerformanceCounter("HowManyBigOrderWeHave", "Count how many big order we have", PerformanceCounterType.NumberOfItems32, PerformanceCounterAttribute.PerformanceCounterUpdateMethod.Update_Increment)] public string PlaceBigOrder(object po) { BigOrder bigPO = new BigOrder(po); bigPO.Save(); return bigPO.PoNumber; } }
[PerformanceCounterCategory("MyBusinessApplication",
"My Business Application Performance Counter Category")]
public class SomeBusinessSerivceProvider {
[PerformanceCounter("HowFastWeMakeTheMoney",
"Count how long we fill an big order",
PerformanceCounterType.RateOfCountsPerSecond32,
PerformanceCounterAttribute.PerformanceCounterUpdateMethod.Update_Increment)]
[PerformanceCounter("HowManyBigOrderWeHave",
"Count how many big order we have",
PerformanceCounterType.NumberOfItems32,
public string PlaceBigOrder(object po) {
BigOrder bigPO = new BigOrder(po);
bigPO.Save();
return bigPO.PoNumber;
}
這個範例中, 我有個重要的商業物件, 我非常關心這個商業物件的商業行為 "PlaceBigOrder"
因此 透過架構與設計好的元件, 負責開發的人員 只要在 Class 宣告上 定義 "某一個 Performance Counter Category", 並且在關心的 Method 上加上 我要追蹤的 "Counter" 有哪些 ! 其他就交給 我這個架構師所設計的 魔術元件
系統上線時 ! 我可以在 作業系統的 Performance Counter 上欣賞, 我的系統穩定的運作~ 我的 大單不斷的敲進來 ~~~ 讚.
上圖中, 綠色線條 表示 目前我已經累積了多少大單跑進來了~ 而紅色的線條更重要了, 首先他等於是系統的"心跳"! 如果他不跳了 表示系統可能出現嚴重停擺或當機 !!! 其次, 如果沒有心跳了 表示我的 大單 $$ 就不進來了 !!! :(
很酷的概念.. 很不錯的設計架構吧 ~ 接下來欣賞一下. 包裝過後的 魔術 元件使用方式!
SomeBusinessSerivceProvider target = new SomeBusinessSerivceProvider(); actual = target.PlaceBigOrder(po); // 透過我的商業物件服務 下單 PerformanceCounterUtilities.UpdatePerformanceCounter(target);
SomeBusinessSerivceProvider target = new SomeBusinessSerivceProvider();
actual = target.PlaceBigOrder(po); // 透過我的商業物件服務 下單 PerformanceCounterUtilities.UpdatePerformanceCounter(target);
只剩下一行程式碼, 就可以把你的 商業物件, 動態 Reflect 出是否有需要 Performance Counter 的服務 ~ 如果要 這個 PerformanceCounterUtilities 物件會幫你自動的建立與產生!
各位可以去下載上述文章覆上的範例
我所異動的部分, 其實是 刪除很多 以及加上了一些我要的部分
其中最重要的是
Public Overloads Shared Sub UpdatePerformanceCounter(ByVal oInput As Object) Dim ObjectType As Type = oInput.GetTypeDim CounterCategory As PerformanceCounterCategoryAttribute = Attribute.GetCustomAttribute(ObjectType, GetType(PerformanceCounterCategoryAttribute)) If CounterCategory Is Nothing ThenIf PerformanceCounterUtilitiesTracing.TraceWarning ThenTrace.WriteLine("The object passed in does have a <PerformaceCounterCategoryAttribute()> attribute", ObjectType.ToString)End IfDebug.Fail(" The object passed in does have a <PerformaceCounterCategoryAttribute()> attribute ")Exit SubEnd If '\\ Get the property attributesDim ObjectProperties() As PropertyInfo = ObjectType.GetProperties(BindingFlags.Instance Or BindingFlags.Public)Dim ObjectMethods() As MethodInfo = ObjectType.GetMethods(BindingFlags.Instance Or BindingFlags.Public Or BindingFlags.NonPublic) Dim PerformanceCounterAttributes As New Collection For Each pi As MethodInfo In ObjectMethodsIf pi.DeclaringType Is ObjectType ThenDim pc() As PerformanceCounterAttribute = pi.GetCustomAttributes(GetType(PerformanceCounterAttribute), False)For Each Item As PerformanceCounterAttribute In pcPerformanceCounterAttributes.Add(New MethodOrPropertyToCounterAttributeLink(Item, pi), item.Name)Next End IfNext For Each pi As PropertyInfo In ObjectPropertiesIf pi.DeclaringType Is ObjectType ThenDim pc() As PerformanceCounterAttribute = pi.GetCustomAttributes(GetType(PerformanceCounterAttribute), False)If pc.Length > 0 ThenPerformanceCounterAttributes.Add(New MethodOrPropertyToCounterAttributeLink(pc.GetValue(0), pi), pi.Name)End IfEnd IfNext If Not PerformanceCounterCategory.Exists(CounterCategory.Name) ThenRebuildPerformanceCounterCategory(oInput)End If Dim Category As New PerformanceCounterCategoryCategory.CategoryName = CounterCategory.Name For Each Counter As MethodOrPropertyToCounterAttributeLink In PerformanceCounterAttributesTryIf Category.CounterExists(Counter.Attribute.Name) ThenDim CounterInstance As New PerformanceCounter(CounterCategory.Name, Counter.Attribute.Name, "")CounterInstance.ReadOnly = FalseIf Counter.Attribute.UpdateMethod = PerformanceCounterAttribute.PerformanceCounterUpdateMethod.Update_Increment ThenCounterInstance.Increment()ElseIf Counter.Attribute.UpdateMethod = PerformanceCounterAttribute.PerformanceCounterUpdateMethod.Update_IncrementBy ThenCounterInstance.IncrementBy(RawValueFromProperty(Counter.linkInfo, oInput))ElseCounterInstance.RawValue = RawValueFromProperty(Counter.linkInfo, oInput)End IfCounterInstance.Close()ElseIf PerformanceCounterUtilitiesTracing.TraceError ThenTrace.WriteLine("Counter instance does not exist", Counter.Attribute.Name)End IfDebug.Fail("Counter instance does not exist")End IfCatch ex As ExceptionIf PerformanceCounterUtilitiesTracing.TraceError ThenTrace.WriteLine(ex.ToString, Counter.Attribute.Name)End IfDebug.Fail("Counter instance correctly not created")End TryNext End Sub 原先範例中PropertyToCounterAttributeLink物件我一點也不喜歡, 所以改為更 一般性的
Public Overloads Shared Sub UpdatePerformanceCounter(ByVal oInput As Object)
Dim ObjectType As Type = oInput.GetTypeDim CounterCategory As PerformanceCounterCategoryAttribute = Attribute.GetCustomAttribute(ObjectType, GetType(PerformanceCounterCategoryAttribute))
If CounterCategory Is Nothing ThenIf PerformanceCounterUtilitiesTracing.TraceWarning ThenTrace.WriteLine("The object passed in does have a <PerformaceCounterCategoryAttribute()> attribute", ObjectType.ToString)End IfDebug.Fail(" The object passed in does have a <PerformaceCounterCategoryAttribute()> attribute ")Exit SubEnd If
'\\ Get the property attributesDim ObjectProperties() As PropertyInfo = ObjectType.GetProperties(BindingFlags.Instance Or BindingFlags.Public)Dim ObjectMethods() As MethodInfo = ObjectType.GetMethods(BindingFlags.Instance Or BindingFlags.Public Or BindingFlags.NonPublic)
Dim PerformanceCounterAttributes As New Collection
For Each pi As MethodInfo In ObjectMethodsIf pi.DeclaringType Is ObjectType ThenDim pc() As PerformanceCounterAttribute = pi.GetCustomAttributes(GetType(PerformanceCounterAttribute), False)For Each Item As PerformanceCounterAttribute In pcPerformanceCounterAttributes.Add(New MethodOrPropertyToCounterAttributeLink(Item, pi), item.Name)Next
End IfNext
For Each pi As PropertyInfo In ObjectPropertiesIf pi.DeclaringType Is ObjectType ThenDim pc() As PerformanceCounterAttribute = pi.GetCustomAttributes(GetType(PerformanceCounterAttribute), False)If pc.Length > 0 ThenPerformanceCounterAttributes.Add(New MethodOrPropertyToCounterAttributeLink(pc.GetValue(0), pi), pi.Name)End IfEnd IfNext
If Not PerformanceCounterCategory.Exists(CounterCategory.Name) ThenRebuildPerformanceCounterCategory(oInput)End If
Dim Category As New PerformanceCounterCategoryCategory.CategoryName = CounterCategory.Name
For Each Counter As MethodOrPropertyToCounterAttributeLink In PerformanceCounterAttributesTryIf Category.CounterExists(Counter.Attribute.Name) ThenDim CounterInstance As New PerformanceCounter(CounterCategory.Name, Counter.Attribute.Name, "")CounterInstance.ReadOnly = FalseIf Counter.Attribute.UpdateMethod = PerformanceCounterAttribute.PerformanceCounterUpdateMethod.Update_Increment ThenCounterInstance.Increment()ElseIf Counter.Attribute.UpdateMethod = PerformanceCounterAttribute.PerformanceCounterUpdateMethod.Update_IncrementBy ThenCounterInstance.IncrementBy(RawValueFromProperty(Counter.linkInfo, oInput))ElseCounterInstance.RawValue = RawValueFromProperty(Counter.linkInfo, oInput)End IfCounterInstance.Close()ElseIf PerformanceCounterUtilitiesTracing.TraceError ThenTrace.WriteLine("Counter instance does not exist", Counter.Attribute.Name)End IfDebug.Fail("Counter instance does not exist")End IfCatch ex As ExceptionIf PerformanceCounterUtilitiesTracing.TraceError ThenTrace.WriteLine(ex.ToString, Counter.Attribute.Name)End IfDebug.Fail("Counter instance correctly not created")End TryNext
End Sub
原先範例中PropertyToCounterAttributeLink物件我一點也不喜歡, 所以改為更 一般性的
Private Class MethodOrPropertyToCounterAttributeLinkPublic Attribute As PerformanceCounterAttributePublic linkInfo As MemberInfo Public Sub New(ByVal AttributeIn As PerformanceCounterAttribute, ByVal mInfo As MemberInfo)Attribute = AttributeInlinkInfo = mInfoEnd SubEnd Class
Private Class MethodOrPropertyToCounterAttributeLinkPublic Attribute As PerformanceCounterAttributePublic linkInfo As MemberInfo
Public Sub New(ByVal AttributeIn As PerformanceCounterAttribute, ByVal mInfo As MemberInfo)Attribute = AttributeInlinkInfo = mInfoEnd SubEnd Class
下載 Sample Code. (Ps. 因為某些原因,這元件已經一段時間沒 update, 我只把它 upgrade 到.NET 2.0 中, 並且只有改了我在 Test Case 中有驗證過的 API, 其他的 API 一定會有 bug 等需要調整, 要靠你自行修正:) 因為 這也是 Open Source 囉)