使用性能計數器監控應用程序

監控應用程序的性能的一個很好的辦法就是使用Performance Counter。Windows提供了系統工具來顯示PerformanceCounter收集的數據,在運行對話框中輸入perfmon,就可以調出該工具來顯示PerformanceCounter收集的數據。如果在工具中添加一個Performance Counter,就會發現,每一個Performance Counter都會屬於一個目錄,以便於組織Performance Counter。每一個目錄下都有許多個Performance Counter,每一個Performance Counter又會有很多實例,例如:在Process目錄下,有很多Performance Counter,其中一個是%ProcessTime,表示CPU佔用時間的Performance Counter,選擇此Performance Counter,又會看到很多實例,每個實例的名字就是進程的名字,選擇一個實例,就可以看到指定進程佔用CPU時間的Performance Counter數據。有時候你會發現選擇一個Performance Counter後並沒有與之相關的任何實例,這表示該Performance Counter只能有一個實例。
在.Net中讀取和寫入PerformanceCounter數據都非常簡單。在操作PerformanceCounter數據之前,首先要創建PerformanceCounterCategory,這只是對PerformanceCounter的一個簡單分類,以便於查看PerformanceCounter數據。由於目錄不能重複創建,所以需要在創建之前檢測目錄是否存在,如果存在可以繼續使用,也可以對其進行刪除,然後再創建它。對目錄的刪除和創建都需要管理員權限,所以,假如運行應用程序的賬戶不是管理員,操作目錄就會拋出異常。所以應該在安裝的時候創建目錄,運行的時候只需要讀取目錄信息就可以了。
string categoryName = "My Service";
if (PerformanceCounterCategory.Exists(categoryName))
{
PerformanceCounterCategory.Delete(categoryName);
}
上面的代碼展示瞭如何檢測和刪除目錄。
對於創建目錄,我們需要指定如下信息:
• 目錄名
• 幫助描述,用於幫助用戶瞭解此目錄的用途
• 目錄類型,目錄類型是一個枚舉類型,具有如下定義:
public enum PerformanceCounterCategoryType
{
Unknown = -1,
SingleInstance = 0,
MultiInstance = 1,
}
SingleInstance表示目錄內部的PerformanceCounter只能有一個實例。MultiInstance表示目錄內部的PerformanceCounter可以有多個實例,如果指定了SingleInstance,則在創建PerformanceCounter實例的時候,不需要指定InstanceName屬性,否則會拋出異常。按照我的理解,此屬性更應該屬於CounterCreationData類。此類用於設置PerformanceCounter相關屬性。
• 一個CounterCreationDataCollection的實例
此集合對象包含了創建PerformanceCounter的信息,它的項的類型是CounterCreationData,此類型的實例中,我們可以指定CounterName,CounterType等屬性,有了這些信息我們就可以在安裝時定義PerformanceCounter。而運行時,就可以根據定義創建PerformanceCounter實例來讀取或者寫入性能數據。
CounterCreationDataCollection ccdc = new CounterCreationDataCollection();
CounterCreationData ccd = new CounterCreationData();
ccd.CounterName = "# of Request";
ccd.CounterType = PerformanceCounterType.NumberOfItems32;
ccdc.Add(ccd);

ccd = new CounterCreationData();
ccd.CounterName = "# of Event";
ccd.CounterType = PerformanceCounterType.NumberOfItems32;
ccdc.Add(ccd);

var category = PerformanceCounterCategory.Create(
categoryName,
"Monitor Performance of My Service",
PerformanceCounterCategoryType.SingleInstance,
ccdc);
上面的代碼創建了包含兩個PerformanceCounter的目錄。通過Perfmon工具,可以看到我們創建的目錄和PerformanceCounter,如果將其添加到監控面板,我們會發現圖表中沒有任何數據。這是因爲我們還沒有向其寫入數據。
下面的代碼將會寫入性能數據,此代碼只是爲了測試PerformanceCounter。
PerformanceCounter pc = new PerformanceCounter();
pc.CategoryName = categoryName;
pc.CounterName = counterName;
//pc.InstanceName = "# of Session Request";
pc.ReadOnly = false;
pc.RawValue = 0;

Timer timer = new Timer(
delegate
{
long nextValue = pc.RawValue + 1;
if (nextValue > 1000)
{
pc.RawValue = 0;
}
else
{
pc.RawValue = nextValue;
}
},
null,
10,
10);
Console.WriteLine("Press any key to exit...");
Console.ReadKey();
我們讓性能數據每次遞增1,直到遞增到1000,完成後再重新從0開始。很容易想象此中數據的線圖形狀,從perfmon工具中查看此PerformanceCounter,會發現圖像基本相同,但不會完全準確,因爲其中涉及到一個採樣頻率的問題,不太可能做到完全準確。
上面的代碼中,我們使用那個RawValue屬性直接設置了性能數據。但是此屬性並不是線程安全的。如果想要在多個線程中同時寫入同一個PerformanceCounter,那麼就需要使用如下三個方法:
public long Decrement();
public long Increment();
public long IncrementBy(long value);
這三個方法以線程安全的方式寫入性能數據。對value傳遞負值,可以對當前性能計數做減法。
使用Performance Counter中,最重要的一個屬性就是PerformanceCounterType,此屬性定義在CounterCreationData中。此屬性決定了性能計數的含義。
對於前面的示例,我們使用了NumberOfItems32類型,此類型表示我們寫入的性能計數是一個整數,表示項的個數。還有其他三個類型也表示相同的含義,NumberOfItems64,NumberOfItemsHEX32,NumberOfItemsHEX64。它們之間的差別只是整數的數值範圍和表示形式。
RateOfCountsPerSecond32類型的PerformanceCounter表示在取樣間隔的每一秒內完成的操作的平均數目。如下所示,創建一個類型爲RateOfCountsPerSecond32的PerformanceCounter:
CounterCreationData ccd = new CounterCreationData();
ccd.CounterName = "Operations per second";
ccd.CounterType = PerformanceCounterType.RateOfCountsPerSecond32;

PerformanceCounter pc1 = new PerformanceCounter();
pc1.CategoryName = categoryName;
pc1.CounterName = "Operations per second";
pc1.ReadOnly = false;
pc1.RawValue = 0;
下面的代碼模擬一個操作,該操作耗時10毫秒,並且被一直調用。
ThreadPool.QueueUserWorkItem(delegate
{
int opTime = 10;
while (true)
{
pc1.Increment();
Thread.Sleep(opTime);
}
});
檢測此性能計數,會發現性能曲線圖是一條直線,每個採樣點的值大概爲100,這是因爲我們每秒模擬調用操作100次。RateOfCountsPerSecond64類型與RateOfCountsPerSecond32具有相同的含義。
CountPerTimeInterval32和CountPerTimeInterval64表示兩次採樣數據與採樣間隔的比值,類似於加速度值。例如第一次採樣時,隊列裏有100個消息,第二次採樣的時候有200個消息,兩次採樣間隔爲1秒,那麼採樣值爲200-100/1 = 100,個人認爲這個性能計數類型幾乎沒啥用處。
RawFraction是一個很有用的計數類型,它表達一個資源使用率的概念。它必須要與RawBase結合使用。RawFraction代表的PerformanceCounter表達正在使用的資源量,RawBase表達的是總資源量。例如系統中內存使用計數器,表達的是已經使用的內存和總內存的比值。具體的計算公式是:(使用的資源/總資源)×100。在創建RawFraction類型的PerformanceCounter時,必須在其後定義RawBase類型的PerformanceCounter。如下:
CounterCreationDataCollection ccdc = new CounterCreationDataCollection();
CounterCreationData ccd = new CounterCreationData();
ccd.CounterName = "Message Count";
ccd.CounterType = PerformanceCounterType.RawFraction;
ccdc.Add(ccd);

ccd = new CounterCreationData();
ccd.CounterName = "Max Message Count";
ccd.CounterType = PerformanceCounterType.RawBase;
ccdc.Add(ccd);
創建PerformanceCounter實例的代碼如下:
PerformanceCounter pc1 = new PerformanceCounter();
pc1.CategoryName = categoryName;
pc1.CounterName = "Message Count";
pc1.ReadOnly = false;
pc1.RawValue = 0;

PerformanceCounter pcb1 = new PerformanceCounter();
pcb1.CategoryName = categoryName;
pcb1.CounterName = "Max Message Count";
pcb1.ReadOnly = false;
pcb1.RawValue = 10000;
總資源量既可以是固定的,也可以是可變的,這取決於具體的需求,如果資源表示的是內存,那麼總資源就不太可能是變化的。如下代碼模擬一個使用的資源一直在增加的場景:
ThreadPool.QueueUserWorkItem(delegate
{
int opTime = 100;
while (true)
{
pc1.Increment();
Thread.Sleep(opTime);
}
});
可以看到,總資源量爲10000,每100毫秒資源使用量都加1,所以資源將在1000秒後耗盡。我們看到的線圖是一個是一個一直增長的曲線。
在測試上述代碼的時候,一定要先創建性能計數器。然後再次啓動進程寫入性能數據。性能計數器不能創建後立刻使用,立刻使用將會導致數據無法被正確處理。這也正是爲什麼微軟推薦在安裝包中創建性能計數器的原因。
AverageTimer32類型表示完成一個操作所需要的時間,時間單位是秒。該類型需要必須與AverageBase類型配合使用,後者表示操作個數,前者表示操作總時間。兩者相除就是單個操作所需時間。示例代碼如下:
ThreadPool.QueueUserWorkItem(delegate
{
int opTime = 100;
while (true)
{
pc1.RawValue = Stopwatch.GetTimestamp();
pcb1.Increment();
Thread.Sleep(opTime);
}
});
上述代碼模擬了一個100毫秒的操作。那麼輸出的採樣值就應該爲0.1秒。個人感覺這個性能計數只在連續調用的時候纔有意義。假如調用不是連續的,那麼兩次採樣的值應該包含中間的空閒時間,顯然這樣的採樣值是毫無意義的,並不能代表單個操作的執行時間。
AverageCount64類型與AverageBase類型聯合使用表示每個操作處理了多少項。例如在十次操作中總共發送了10k字節,那麼採樣值爲10k/10=1k。表示每次操作平均發送1k字節。AverageBase表示兩次採樣間的操作數,AverageCount64表示兩次採樣間處理的總項數。
CounterDelta32類型表示兩次採樣之間的差異,CounterDelta64類型具有相同的功能。(不知道具體應用在哪種場景?)
SampleFraction類型和SampleBase類型的計數器結合使用,可以獲取最近兩個採用時間間隔內的命中與所有操作的平均比率。公式:((N 1 - N 0) / (D 1 - D 0)) x 100。此計數可以表示進程中用戶佔用的CPU時間和全部CPU時間之間的比例。還可以表示緩存命中次數和全部緩存探測次數的比例,即緩存命中率。
看上去SampleCounter和RateOfCountsPerSecond32之間沒有什麼分別?
Timer100Ns類型的計數器表示佔用時間百分比。它以 100 毫微秒 (ns) 爲單位來測量時間。公式爲(N 1 - N 0) / (D 1 - D 0) x 100,其中分子表示活動狀態的時間,分母表示取樣時間間隔的總運行時間。這種類型的計數器包括 Processor\ % User Time。具體來說,分子表示用戶CPU時間,分母表示總時間間隔,即兩次調用Increment方法的時間間隔。鑑於此,此計數器的RawValue屬性必須爲時間,以100ns爲單位,也就是DateTime中的Ticks的單位。Timer100NsInverse類型的計數器是Timer100Ns的反向計數器,公式爲:(1- ((N 1 - N 0) / (D 1 - D 0))) x 100,表示非活動時間的佔用百分比。這種類型的計數器包括 Processor\ % Processor Time。
CounterTimer和CounterTimerInverse類型的計數器與上面的兩個計數器相對應,他們之間的區別是時間單位。CounterTimer和CounterTimerInverse類型的計數器的時間單位是系統的Tick,而不是100ns。
ElapsedTime計數器表示系統的啓動時間。公式:(D 0 - N 0) / F,其中 D 0 表示啓動結束時間,N 0 表示對象的啓動開始時間,F 表示一秒內經過的時間單位數。 F表示需要使用系統的Tick刻度,而不是標準的Tick刻度。系統的。Tick刻度是Stopwatch.GetTimestamp方法返回值的刻度。標註的Tick刻度是DateTime的Ticks的刻度。這種類型的計數器包括 System\ System Up Time。個人認爲這種計數器幾乎無用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章