Ответ 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()
для вас вызов, который вернет текущее значение RequestsPerSecondPerformanceCounter
.
Преимущества этой реализации заключаются в том, что вы создаете счетчик производительности только один раз, а затем продолжаете использовать, пока не закончите с ним. Его простота в использовании, потому что вы просто вызываете 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, чтобы узнать, является ли это непрерывным счетом, средним, скользящим средним и т.д.... и соответствующим образом скорректируйте свою реализацию.