Антенка привела вас в правильном направлении.Вы не должны утилизировать и заново создавать счетчик производительности при каждом обновлении / запросе стоимости.За создание экземпляров счетчиков производительности взимается плата, и первое чтение может быть неточным, как указано в приведенной ниже цитате.Кроме того, ваши 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
}
Хорошо, теперь для некоторого объяснения.
- Сначала вы заметите, что этот класс разработан как статический синглтон.Вы не можете загрузить несколько его копий, у него есть личный конструктор и он с нетерпением инициализирует свой внутренний экземпляр.Это гарантирует, что вы случайно не создадите несколько копий одного и того же
PerformanceCounter
. - . Далее вы заметите, что в приватном конструкторе (это будет выполнено только один раз при первом обращении к классу), мы создаем оба
PerformanceCounter
и таймер, который будет использоваться для опроса PerformanceCounter
. - Метод обратного вызова Timer создаст
PerformanceCounter
, если необходимо, и получит следующее доступное значение.Также каждые 5 итераций мы будем видеть, сколько времени прошло с момента вашего последнего запроса значения PerformanceCounter
.Если прошло более 5 секунд, мы отключим таймер опроса, так как он сейчас не нужен.Мы всегда можем запустить его позже, если понадобится снова. - Теперь у нас есть статический метод с именем
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, чтобы узнать, является ли счет непрерывным, средним, скользящим средним и т. д... и соответственно скорректируйте свою реализацию.