2011-11-17 21 views
12

我有以下類返回每秒當前請求的IIS數。我每分鐘調用一次RefreshCounters來保持Requests Per Second值刷新(因爲它是平均值,如果我保持它太長,那麼舊的值會影響結果太多);當我需要顯示當前RequestsPerSecond時,我會調用該屬性。性能計數器 - System.InvalidOperationException:類別不存在

public class Counters 
{ 
    private static PerformanceCounter pcReqsPerSec; 
    private const string counterKey = "Requests_Sec"; 
    public static object RequestsPerSecond 
    { 
     get 
     { 
      lock (counterKey) 
      { 
       if (pcReqsPerSec != null) 
        return pcReqsPerSec.NextValue().ToString("N2"); // EXCEPTION 
       else 
        return "0"; 
      } 
     } 
    } 

    internal static string RefreshCounters() 
    { 
     lock (counterKey) 
     { 
      try 
      { 
       if (pcReqsPerSec != null) 
       { 
        pcReqsPerSec.Dispose(); 
        pcReqsPerSec = null; 
       } 

       pcReqsPerSec = new PerformanceCounter("W3SVC_W3WP", "Requests/Sec", "_Total", true); 
       pcReqsPerSec.NextValue(); 

       PerformanceCounter.CloseSharedResources(); 

       return null; 
      } 
      catch (Exception ex) 
      { 
       return ex.ToString(); 
      } 
     } 
    } 
} 

的問題在於以下異常有時拋出:

System.InvalidOperationException: Category does not exist. 

at System.Diagnostics.PerformanceCounterLib.GetCategorySample(String machine,\ String category) 
at System.Diagnostics.PerformanceCounter.NextSample() 
at System.Diagnostics.PerformanceCounter.NextValue() 
at BidBop.Admin.PerfCounter.Counters.get_RequestsPerSecond() in [[[pcReqsPerSec.NextValue().ToString("N2");]]] 

難道我不打烊的PerformanceCounter以前的情況正常嗎?我做錯了什麼,以至於有時候我最終會遇到這種異常?

編輯: 和公正的記錄,我主持在IIS網站這個類(那是當然的,在應用程序池具有管理權限的託管)和調用從ASMX服務方法。使用計數器值(顯示它們)的站點每1分鐘調用一次RefreshCounters,每5秒調用一次RequestsPerSecond; RequestPerSecond在呼叫之間緩存。

我每隔1分鐘就會調用一次RefreshCounters,因爲值往往會變得「過時」 - 也受較舊值(例如實際爲1分鐘前)的影響。

+0

和公正的記錄,我主持這個類中IIS網站(那當然是在應用程序池具有管理權限的託管)和ASMX服務調用方法... – kape123

回答

15

Antenka帶領你在這裏一個很好的方向。您不應該在每次更新/價值請求時處理和重新創建性能計數器。實例化性能計數器需要花費,並且第一次讀取可能不準確,如下面的引用中所示。你的lock() { ... }陳述非常廣泛(他們涵蓋了很多陳述),並且會很慢。最好讓你的鎖儘可能小。我正在給Antenka一張質量參考和好建議的投票!

不過,我想我可以爲你提供一個更好的答案。我在監控服務器性能方面有相當多的經驗,並能準確理解您的需求。你的代碼沒有考慮到的一個問題是,無論顯示性能計數器的任何代碼(.aspx,.asmx,控制檯應用程序,winform應用程序等)都可能以任何速率請求此統計信息;它可以每10秒請求一次,也許每秒5次,你不知道也不應該在意。因此,您需要將PerformanceCounter集合代碼與實際報告當前Requests/Second值的代碼進行監視。出於性能方面的原因,我還將向您展示如何在第一次請求時設置性能計數器,然後繼續執行,直到沒有人提出任何請求5秒鐘,然後正確關閉/處理PerformanceCounter。

public class RequestsPerSecondCollector 
{ 
    #region General Declaration 
    //Static Stuff for the polling timer 
    private static System.Threading.Timer pollingTimer; 
    private static int stateCounter = 0; 
    private static int lockTimerCounter = 0; 

    //Instance Stuff for our performance counter 
    private static System.Diagnostics.PerformanceCounter pcReqsPerSec; 
    private readonly static object threadLock = new object(); 
    private static decimal CurrentRequestsPerSecondValue; 
    private static int LastRequestTicks; 
    #endregion 

    #region Singleton Implementation 
    /// <summary> 
    /// Static members are 'eagerly initialized', that is, 
    /// immediately when class is loaded for the first time. 
    /// .NET guarantees thread safety for static initialization. 
    /// </summary> 
    private static readonly RequestsPerSecondCollector _instance = new RequestsPerSecondCollector(); 
    #endregion 

    #region Constructor/Finalizer 
    /// <summary> 
    /// Private constructor for static singleton instance construction, you won't be able to instantiate this class outside of itself. 
    /// </summary> 
    private RequestsPerSecondCollector() 
    { 
     LastRequestTicks = System.Environment.TickCount; 

     // Start things up by making the first request. 
     GetRequestsPerSecond(); 
    } 
    #endregion 

    #region Getter for current requests per second measure 
    public static decimal GetRequestsPerSecond() 
    { 
     if (pollingTimer == null) 
     { 
      Console.WriteLine("Starting Poll Timer"); 

      // Let's check the performance counter every 1 second, and don't do the first time until after 1 second. 
      pollingTimer = new System.Threading.Timer(OnTimerCallback, null, 1000, 1000); 

      // The first read from a performance counter is notoriously inaccurate, so 
      OnTimerCallback(null); 
     } 

     LastRequestTicks = System.Environment.TickCount; 
     lock (threadLock) 
     { 
      return CurrentRequestsPerSecondValue; 
     } 
    } 
    #endregion 

    #region Polling Timer 
    static void OnTimerCallback(object state) 
    { 
     if (System.Threading.Interlocked.CompareExchange(ref lockTimerCounter, 1, 0) == 0) 
     { 
      if (pcReqsPerSec == null) 
       pcReqsPerSec = new System.Diagnostics.PerformanceCounter("W3SVC_W3WP", "Requests/Sec", "_Total", true); 

      if (pcReqsPerSec != null) 
      { 
       try 
       { 
        lock (threadLock) 
        { 
         CurrentRequestsPerSecondValue = Convert.ToDecimal(pcReqsPerSec.NextValue().ToString("N2")); 
        } 
       } 
       catch (Exception) { 
        // We had problem, just get rid of the performance counter and we'll rebuild it next revision 
        if (pcReqsPerSec != null) 
        { 
         pcReqsPerSec.Close(); 
         pcReqsPerSec.Dispose(); 
         pcReqsPerSec = null; 
        } 
       } 
      } 

      stateCounter++; 

      //Check every 5 seconds or so if anybody is still monitoring the server PerformanceCounter, if not shut down our PerformanceCounter 
      if (stateCounter % 5 == 0) 
      { 
       if (System.Environment.TickCount - LastRequestTicks > 5000) 
       { 
        Console.WriteLine("Stopping Poll Timer"); 

        pollingTimer.Dispose(); 
        pollingTimer = null; 

        if (pcReqsPerSec != null) 
        { 
         pcReqsPerSec.Close(); 
         pcReqsPerSec.Dispose(); 
         pcReqsPerSec = null; 
        } 
       }              
      } 

      System.Threading.Interlocked.Add(ref lockTimerCounter, -1); 
     } 
    } 
    #endregion 
} 

好吧,現在來解釋一下。

  1. 首先你會注意到這個類被設計成一個靜態單例。 你不能加載它的多個副本,它有一個私人構造函數 和和急切地初始化它自己的內部實例。這使得 確保您不會無意中創建相同的多個副本 PerformanceCounter
  2. 接下來,您將在私有構造通知(這將只運行一次 時,第一次訪問類),我們同時創建 PerformanceCounter,並且將用於輪詢 PerformanceCounter一個計時器。
  3. 如果需要 並且獲取其下一個值可用,則計時器的回撥方法將創建PerformanceCounter。同樣每5次迭代 我們將看到自從您最後一次請求 PerformanceCounter的值以來多長時間。如果超過5秒,我們將 關閉輪詢定時器,因爲此時它不需要。如果我們再次需要它,我們可以在以後再次啓動它。
  4. 現在我們有一個靜態方法叫GetRequestsPerSecond()爲您 通話將返回RequestsPerSecond PerformanceCounter的當前值。

這個實現的好處是你只創建一次性能計數器,然後繼續使用,直到你完成它。它易於使用,因爲您可以隨時隨地撥打RequestsPerSecondCollector.GetRequestsPerSecond()(.aspx,.asmx,控制檯應用程序,winforms應用程序等)。總是隻有一個PerformanceCounter,並且無論您多麼快速地撥打RequestsPerSecondCollector.GetRequestsPerSecond(),它總是會以每秒精確1次的速度進行輪詢。如果您在5秒鐘之內未請求數值,它也會自動關閉並處理PerformanceCounter。當然,您可以調整定時器間隔和超時毫秒以滿足您的需求。您可以在60秒而不是5秒內輪詢更快和超時。我選擇了5秒鐘,因爲它證明了在visual studio中進行調試時它可以非常快速地工作。一旦你測試它並知道它有效,你可能需要更長的超時時間。

希望這可以幫助您不僅更好地使用PerformanceCounters,而且還可以安全地重用此類,它與您希望顯示統計信息的任何內容相分離。可重複使用的代碼始終是一個優點!

編輯:作爲一個後續問題,如果您想在性能計數器運行時每60秒執行一次清理或保姆任務該怎麼辦?那麼我們已經有計時器每1秒運行一次,並且有一個變量跟蹤我們的循環迭代,稱爲stateCounter,它在每個計時器回調時遞增。所以,你可以加入一些像這樣的代碼:

// Every 60 seconds I want to close/dispose my PerformanceCounter 
if (stateCounter % 60 == 0) 
{ 
    if (pcReqsPerSec != null) 
    { 
     pcReqsPerSec.Close(); 
     pcReqsPerSec.Dispose(); 
     pcReqsPerSec = null; 
    } 
} 

我要指出的是,在這個例子性能計數器不應該「去陳舊」。我相信'請求/秒'應該是一個平均而不是一個移動平均統計。但是這個示例只是說明了您在某個固定時間間隔內對PerformanceCounter進行任何類型的清理或「保姆」的方式。在這種情況下,我們正在關閉並釋放性能計數器,這將導致它在下一次計時器回調時被重新創建。您可以根據您的使用情況並根據您使用的特定PerformanceCounter對其進行修改。閱讀這個問題/答案的大多數人不需要這樣做。檢查您所需的PerformanceCounter的文檔,看它是否是連續計數,平均值,移動平均值等,並適當調整您的實施。

+2

男人......你的回答很棒;)。請閱讀編輯(我已經解釋了託管上下文)並儘可能編輯您的答案。有一件事特別困擾我(並且是我有RefreshCounters方法的原因) - pcReqsPerSec.NextValue()變成「陳舊」,如果我不每分鐘都重新創建計數器...是這個計數器是如何工作的還是我在做有問題?有沒有辦法只從最後一分鐘獲得AVG而不重新創建PerfCounter? – kape123

+0

@BenSwayne,哇..真的很好,乾淨,容易理解的解決方案和理論!從我這裏接受upvote :) – Antenka

+0

@ kape123我更新了答案,以反映您希望每60秒重新創建一次性能計數器的願望。我不認爲它是必要的,但如果這是你想要的,你當然可以做到! – BenSwayne

0

只是出於好奇,你在Visual Studio中爲屬性設置了什麼?在VS中,轉到項目屬性,構建,平臺目標並將其更改爲AnyCPU。我之前在性能計數器設置爲x86時並不總是檢索的地方看過它,並且將其更改爲AnyCPU可以修復它。

+0

這是AnyCPU ...我正在編譯x64機器並在x64機器上執行代碼。 – kape123

3

我不知道,如果通過你..我讀過的文章PerformanceCounter.NextValue Method

而且有一個評論:

// If the category does not exist, create the category and exit. 
// Performance counters should not be created and immediately used. 
// There is a latency time to enable the counters, they should be created 
// prior to executing the application that uses the counters. 
// Execute this sample a second time to use the category. 

所以,我有一個問題,這可能會導致回答:是不是調用RequestsPerSecond方法發生得太早? 此外,我建議您嘗試檢查類別是否不存在並在某處記錄信息,以便我們可以分析它並確定我們擁有哪些條件以及發生的頻率。

+0

我認爲這個問題可能是事實,counterKey是const - 所以也許鎖定工作不正常。我已經將它更改爲靜態對象counterKey = new object();過去兩天沒有問題。 – kape123

+0

哦,很高興知道它現在是穩定的:) – Antenka

+0

那麼,它仍然有待觀察......如果沒有改變,我會接受你的答案;) – kape123

3

我只是解決了這個類型的錯誤或異常使用的:

使用,

new PerformanceCounter("Processor Information", "% Processor Time", "_Total"); 

相反的,

new PerformanceCounter("Processor", "% Processor Time", "_Total"); 
+2

處理器信息和處理器之間的區別是什麼?爲什麼一個人工作,另一個不工作?我在谷歌上找不到任何解釋。 – Paccc

1

我不得不檢索每秒請求在IIS上使用代碼的問題類似如下

var pc = new PerformanceCounter(); 
pc.CategoryName = @"W3SVC_W3WP"; 
pc.InstanceName = @"_Total"; 
pc.CounterName = @"Requests/Sec"; 
Console.WriteLine(pc.NextValue()); 

這有時會拋出InvalidOperationException,我能夠通過重新啓動IIS來重現異常。如果我用非熱身的IIS運行,例如筆記本電腦重新啓動或IIS重新啓動後,然後我得到此異常。先打網站,事先做好任何http請求,然後等一兩秒鐘,我就不會遇到這種情況。這聽起來像是性能計數器被緩存了,當它們被閒置時,它們會被拋棄,並需要一段時間來重新緩存? (或類似)。

更新1:最初,當我手動瀏覽網站並對其進行預熱時,它解決了問題。我試圖以編程方式預熱服務器new WebClient().DownloadString(); Thread.Sleep()高達3000毫秒,這不起作用?所以我手動預熱服務器的結果,可能在某種程度上是誤報。我在這裏留下我的答案,因爲這可能是原因(即手動升溫),也許別人可以進一步闡述?

Update2:啊,好的,這裏有一些單元測試,總結了我昨天做的進一步實驗的一些學習。 (在這個問題上谷歌沒有很多btw。)

據我可以推理,下面的陳述可能是真實的; (我下面提交單元測試作爲證據。)我可能誤解的結果,所以請仔細檢查;-D

  1. 創建性能計數器並調用的getValue存在的類別之前,例如查詢IIS計數器,而IIS是冷的,沒有進程運行,會拋出InvalidOperation異常「類別不存在」。 (我假設所有計數器都是如此,而不僅僅是IIS)

  2. 在Visual Studio單元測試中,一旦計數器引發異常,如果隨後在第一次異常之後預熱服務器,並創建一個新的PerformanceCounter並再次查詢,它仍然會拋出異常! (這是一個驚喜,我認爲這是因爲一些單身行動。我的歉意我沒有足夠的時間來反編譯來源進一步調查發佈此回覆之前。)

  3. 如上所述2,如果您標記與[STAThread]單元測試,然後我能夠創建一個新的PerformanceCounter失敗後。 (這可能與性能計數器可能是單身有關,需要進一步測試。)

  4. 在創建計數器並使用它之前,我不需要任何暫停,儘管在MSDN相同的代碼文檔中有一些警告,除了在調用NextValue()之前創建性能計數器本身所需的時間之外。預熱櫃檯並帶來「類別」存在,對我來說,在IIS的一鞠躬中,即發出一個GET請求,並且中提琴,不再會得到「InvalidOperationException」,這似乎是一個對我來說可靠的修復,現在。至少在查詢IIS性能計數器時。

CreatingPerformanceCounterBeforeWarmingUpServerThrowsException

[Test, Ignore("Run manually AFTER restarting IIS with 'iisreset' at cmd prompt.")] 
public void CreatingPerformanceCounterBeforeWarmingUpServerThrowsException() 
{ 
    Console.WriteLine("Given a webserver that is cold"); 
    Console.WriteLine("When I create a performance counter and read next value"); 
    using (var pc1 = new PerformanceCounter()) 
    { 
     pc1.CategoryName = @"W3SVC_W3WP"; 
     pc1.InstanceName = @"_Total"; 
     pc1.CounterName = @"Requests/Sec"; 
     Action action1 =() => pc1.NextValue(); 
     Console.WriteLine("Then InvalidOperationException will be thrown"); 
     action1.ShouldThrow<InvalidOperationException>();     
    } 
} 


[Test, Ignore("Run manually AFTER restarting IIS with 'iisreset' at cmd prompt.")] 
public void CreatingPerformanceCounterAfterWarmingUpServerDoesNotThrowException() 
{ 
    Console.WriteLine("Given a webserver that has been Warmed up"); 
    using (var client = new WebClient()) 
    { 
     client.DownloadString("http://localhost:8082/small1.json"); 
    } 
    Console.WriteLine("When I create a performance counter and read next value"); 
    using (var pc2 = new PerformanceCounter()) 
    { 
     pc2.CategoryName = @"W3SVC_W3WP"; 
     pc2.InstanceName = @"_Total"; 
     pc2.CounterName = @"Requests/Sec"; 
     float? result = null; 
     Action action2 =() => result = pc2.NextValue(); 
     Console.WriteLine("Then InvalidOperationException will not be thrown"); 
     action2.ShouldNotThrow(); 
     Console.WriteLine("And the counter value will be returned"); 
     result.HasValue.Should().BeTrue(); 
    } 
} 
+1

如果您遇到問題,您應該考慮發佈自己的問題(也可以引用此問題)。就其本身而言,您的帖子不是對這裏原始問題的回答,因此不屬於此處。這是一個截然不同的問題。 – BenSwayne