Подтвердить что ты не робот

Счетчик производительности - System.InvalidOperationException: Категория не существует

У меня есть следующий класс, который возвращает число текущего запроса в секунду IIS. Я каждую минуту вызываю RefreshCounters, чтобы обновить значение Requests per Second (потому что оно среднее, и если я слишком долго его старую, это слишком сильно повлияет на результат)... и когда мне нужно отобразить текущую 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 правильно? Что я делаю неправильно, так что иногда я получаю исключение?

EDIT: И только для записи я размещаю этот класс на веб-сайте IIS (то есть, конечно, размещен в пуле приложений, который имеет административные привилегии) ​​и вызывает методы из службы ASMX. Сайт, который использует значения счетчика (отображает их), вызывает RefreshCounters каждые 1 минуту и ​​RequestsPerSecond каждые 5 секунд; RequestPerSecond кэшируются между вызовами.

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

4b9b3361

Ответ 1

Аннетка привела вас в хорошем направлении. Вы не должны удалять и воссоздавать счетчик производительности при каждом обновлении/запросе на значение. Существует стоимость для создания экземпляров производительности, и первое чтение может быть неточным, как указано в приведенной ниже цитате. Кроме того, ваши предложения 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 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
}

Хорошо, теперь для некоторых объяснений.

  • Сначала вы заметите, что этот класс разработан как статический синглтон. Вы не можете загрузить несколько копий, у него есть частный конструктор и и с нетерпением инициализировал внутренний экземпляр самого себя. Это делает убедитесь, что вы случайно не создали несколько копий одного и того же PerformanceCounter.
  • Далее вы заметите в частном конструкторе (это будет выполняться только один раз, когда класс открывается первым), мы создаем как PerformanceCounter и таймер, который будет использоваться для опроса PerformanceCounter.
  • Метод обратного вызова таймера создаст PerformanceCounter if необходимо и получить его следующее значение. Также каждые 5 итераций мы увидим, сколько времени прошло с момента вашего последнего запроса на PerformanceCounter. Если прошло более 5 секунд, мы выключите таймер опроса как ненужный на данный момент. Мы можем всегда начинайте его снова, если нам это нужно снова.
  • Теперь у нас есть статический метод GetRequestsPerSecond() для вас вызов, который вернет текущее значение RequestsPerSecond PerformanceCounter.

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

Надеюсь, это поможет вам не только лучше использовать 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;
    }
}

Я должен указать, что этот счетчик производительности в этом примере не должен "стареть". Я считаю, что "Request/Sec" должен быть средней, а не скользящей средней статистикой. Но этот пример просто иллюстрирует способ, которым вы моглиделать любой тип очистки или "няни" вашего PerformanceCounter на регулярном временном интервале. В этом случае мы закрываем и удаляем счетчик производительности, который заставит его воссоздаваться при следующем обратном вызове таймера. Вы можете изменить это для своего варианта использования и в соответствии с конкретным PerformanceCounter, который вы используете. Большинство людей, читающих этот вопрос/ответ, не должны этого делать. Проверьте документацию для желаемого PerformanceCounter, чтобы узнать, является ли это непрерывным счетом, средним, скользящим средним и т.д.... и соответствующим образом скорректируйте свою реализацию.

Ответ 2

Я не знаю, если это пройдет. Я прочитал статью 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 происходит слишком рано? Кроме того, я предлагаю вам попробовать проверить, не существует ли Категория и регистрировать информацию где-то, чтобы мы могли ее проанализировать и определить, какие условия у нас есть и как часто это происходит.

Ответ 3

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

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

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

Вместо <

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

Ответ 4

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

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

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

Насколько я могу рассуждать, могут быть справедливы следующие утверждения: (и я представляю единичные тесты под доказательствами.) Возможно, я неверно истолковал результаты, поэтому, пожалуйста, дважды проверьте; -D

  • Создайте счетчик производительности и вызовите getValue до того, как существует категория, например. запрос IIS-счетчика, в то время как IIS холоден и не работает, будет генерировать исключение InvalidOperation. "Категория не существует". (Я предполагаю, что это верно для всех счетчиков, а не только для IIS.)

  • В Visual Studio unit test, как только ваш счетчик выдает исключение, если вы впоследствии разогреете сервер после первого исключения, а затем создайте новый PerformanceCounter и снова запросите, он все равно выбросит исключение! (это было неожиданностью, я предполагаю, что это из-за какого-то одностороннего действия. Мои извинения у меня не было достаточно времени, чтобы декомпилировать источники для дальнейшего расследования, прежде чем публиковать этот ответ.)

  • В 2 выше, если вы отметили unit test с помощью [STAThread], тогда я смог создать новый PerformanceCounter после того, как он потерпел неудачу. (Возможно, это связано с тем, что счетчик производительности, возможно, является синглоном? Требует дальнейшего тестирования.)

  • Мне не потребовалась пауза, прежде чем создавать счетчик и использовать его, несмотря на некоторые предупреждения в той же кодовой документации 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();
    }
}

Ответ 5

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