Счетчик производительности - System.InvalidOperationException: категория не существует - PullRequest
12 голосов
/ 17 ноября 2011

У меня следующий класс, который возвращает номер текущего запроса в секунду IIS.Я вызываю RefreshCounters каждую минуту, чтобы обновлять значение «Количество запросов в секунду» (потому что оно среднее и если я буду хранить его слишком долго, старое значение будет слишком сильно влиять на результат) ... и когда мне нужно отобразить текущий RequestsPerSecond, я вызываю это свойство.

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.Сайт, который использует значения счетчика (отображает их), вызывает RefreshCounters каждую 1 минуту и ​​RequestsPerSecond каждые 5 секунд;RequestPerSecond кэшируется между вызовами.

Я вызываю RefreshCounters каждую 1 минуту, потому что значения имеют тенденцию становиться «устаревшими» - слишком подвержены влиянию более старых значений (которые действовали 1 минуту назад, например).

Ответы [ 5 ]

16 голосов
/ 25 ноября 2011

Антенка привела вас в правильном направлении.Вы не должны утилизировать и заново создавать счетчик производительности при каждом обновлении / запросе стоимости.За создание экземпляров счетчиков производительности взимается плата, и первое чтение может быть неточным, как указано в приведенной ниже цитате.Кроме того, ваши lock() { ... } заявления очень широки (они охватывают много утверждений) и будут медленными.Лучше, чтобы ваши замки были как можно меньше.Я даю «Антенке» голос за качество и хороший совет!

Тем не менее, я думаю, что могу дать вам лучший ответ.У меня достаточно опыта в мониторинге производительности сервера и я точно понимаю, что вам нужно.Одна проблема, которую ваш код не принимает во внимание, заключается в том, что любой код, отображающий счетчик производительности (.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. Метод обратного вызова Timer создаст PerformanceCounter, если необходимо, и получит следующее доступное значение.Также каждые 5 итераций мы будем видеть, сколько времени прошло с момента вашего последнего запроса значения PerformanceCounter.Если прошло более 5 секунд, мы отключим таймер опроса, так как он сейчас не нужен.Мы всегда можем запустить его позже, если понадобится снова.
  4. Теперь у нас есть статический метод с именем GetRequestsPerSecond() для вызова, который будет возвращать текущее значение RequestsPerSecond PerformanceCounter.

Преимущества этой реализации состоят в том, что вы создаете счетчик производительности только один раз, а затем продолжаете использовать до тех пор, пока не закончите с ним.Его легко использовать, потому что вы просто вызываете RequestsPerSecondCollector.GetRequestsPerSecond() из любой точки мира (.aspx, .asmx, консольное приложение, приложение winforms и т. Д.).Всегда будет только один PerformanceCounter, и он всегда будет опрашиваться ровно 1 раз в секунду, независимо от того, как быстро вы наберете RequestsPerSecondCollector.GetRequestsPerSecond().Он также автоматически закроет и утилизирует PerformanceCounter, если вы не запрашивали его значение более 5 секунд.Конечно, вы можете настроить интервал таймера и время ожидания в миллисекундах в соответствии с вашими потребностями.Вы можете опрашивать быстрее и время ожидания, скажем, 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, чтобы узнать, является ли счет непрерывным, средним, скользящим средним и т. д... и соответственно скорректируйте свою реализацию.

3 голосов
/ 16 сентября 2012

Я только что решил этот тип ошибки или исключения с помощью:

Использование,

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

Вместо

new PerformanceCounter("Processor", "% Processor Time", "_Total");
3 голосов
/ 24 ноября 2011

Не знаю, пройдет ли это мимо вас .. Я прочитал статью Метод PerformanceCounter.NextValue

И был комментарий:

// 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.
* 1007Итак, у меня есть вопрос, который может привести к ответу: разве вызов метода RequestsPerSecond не происходит слишком рано?Кроме того, я бы посоветовал вам попробовать проверить, не существует ли Категория, и записать информацию где-нибудь, чтобы мы могли проанализировать ее и определить, какие у нас условия и как часто это происходит.
1 голос
/ 03 ноября 2014

У меня была проблема с получением запросов в секунду в 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-запрос, подождите секунду или две, и я не получу исключения. Это пахнет, как счетчики производительности кэшируются, и когда в режиме ожидания они сбрасываются, и требуется некоторое время для повторного кэширования? (или похожие).

Update1 : Изначально, когда я вручную захожу на сайт и разогреваю его, это решает проблему. Я попытался программно прогреть сервер с new WebClient().DownloadString(); Thread.Sleep() до 3000 мс, и это не сработало? Таким образом, мои результаты ручного прогрева сервера могут как-то быть ложным срабатыванием. Я оставляю здесь свой ответ, потому что это может быть причиной (то есть ручным прогревом), и, может быть, кто-то еще может уточнить?

Update2 : А, хорошо, вот несколько юнит-тестов, в которых обобщены некоторые уроки, полученные в ходе дальнейших экспериментов, которые я провел вчера. (Кстати, в Google не так много на эту тему.)

Насколько я могу судить, следующие утверждения могут быть правдой; (и я представляю тестовые модули ниже в качестве доказательства.) Возможно, я неверно истолковал результаты, поэтому, пожалуйста, проверьте дважды; -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();
    }
}
0 голосов
/ 20 ноября 2011

Просто из любопытства, что вы установили для свойств в Visual Studio?В VS перейдите в Project Properties, Build, Platform target и измените его на AnyCPU.Я видел это раньше, когда счетчики производительности не всегда извлекаются, когда для него установлено значение x86, и изменение его на AnyCPU может исправить это.

...