July 27, 2005

счётчики производительности

Сегодня хочу расскахать о своём опыте работы с системными счётчиками производительности или, как их называют в оригинале, Performance
counters.

Счётчики эти существуют, насколько мне известно, во всех NT Виндовсах. (Ну, по крайней мере, в 2000, ХР и 2003 точно есть.) Вещь исключительно удобная! "А чем же она хороша?" - спросите вы. А вот чем. Это простой и удобный механизм, который позволяет исследовать временные и количественные характеристики своего приложения. Кроме того, в ОС уже поддерживается большое количество системных счётчиков, по состоянию которых можно судить о производительности самой системы. Более того, так как они являются частью операционной системы, то им есть смысл доверять =) Если злой дядя спросит вас о быстродействии вашего приложения можно показать ему на показания счётчиков (и даже на график, который строится на основе отсчётов) и с полным правом утверждать, что цифры эти не придуманы, а посчитаны механизмом, которому можно доверять.

Рассказ мой будет основываться на использованиия счётчиков производительности в среде Microsoft .Net. Дотнет выбран по нескольким причиинам: во-первых, мне понадобились счётчики именно в приложении .net, а кроме того, я не смог найти в MSDN рассказа об API счётчиков вне .net Framework (хотя, и не особо-то искал).

Рассказывать обо всех наворотах работы со счётчиками я не стану, это сделали уже без меня. Почитать можно вот тут. Ссылка для тех, кому не лень по-англицки. В действительности, там много воды и по сути можно читать только код. А ещё там есть ссылка на MSDN, который все мы любим за оооооочень много инфы.

Основные понятия

Перечислю ключевые слова: категория, счётчик, инстанс.
Категория -- логическая группа счётчиков, которая имеет имя. Счётчик не может существовать вне группы. Каждая группа содержит один и более счётчиков, хотя, наверное, может содержать и 0 счётчиков, хотя кому она такая нужна.
Счётчик -- собственной персоной! Бывают простые, просто меряют количество чего-то, например, каких-то событий в системе или приложении. Бывают сложные, могут мерять среднее значение величины или скорость событий в единицу времени. Единицей времени выступает секунда.
Инстанс -- экземпляр, наверное так можно перевести. Я с ними особо не заморачивался, но идея в том, что по одному имени счётчика можно получать два разных его экземпляра. Отличаться они будут по имени инстанса и значения будут иметь каждый своё. Да, ксати, забыл сказать, но это уже и так ясно, счётчик является системным объектом, и существует в системе в единственном экземпляре! Отсюда вывод: если
одновременно два приложения считают количество обрабатываемых записей, например, то вполне резонно, что значение счётчика будет вдвое больше!

Как использовать счётчики
Начнём с того, что получим уже сущестующий счётчик. Кому как, а мне всегда было интересно узнать uptime моей системы. Я даже для этого специальную утилитку недавно скачал. А теперь вот вам простой код, который говорит аптайм системы в секундах.

using System;
using System.Diagnostics;
using System.Threading;

namespace dwns{
public class UptimeApp{
public static void Main( string[] args ){
PerformanceCounter counter = new PerformanceCounter(
"System", // имя категории
"System Up Time", // имя самого счётчика
true ); // счётчик только для чтения
// интересно, а можно ли открыть системный счётчик и
// поменять в нём значение?! надо будет попробовать

// повторим для верности 10 раз
for( int i = 0; i < 10; ++i ){
// получаем значение счётчика
Console.WriteLine( counter.NextValue() );
Thread.Sleep( 1000 ); // поспим секунду
}
}
}
}

Собственно, всё ясно... Одно только не совсем понятно... Почему первое значение равно 0. Курим MSDN... Ясно! Если значение зависит от двух отсчётов то первый вызов NextValue возвратит 0. Значит всё в порядке. Играемся дальше.

Как создавать свои счётчики
Хватит пользоваться дарами природы. Теперь хотим мерять параметры своего приложения. Например, среднее время обработки чего-нибудь.
Если бы счётчик уже был в системе, то задача сводилась бы к первой и особого интереса не представляла. Предположим, что в системе наш счётчик не зарегистрирован. Надо создавать.

Напомню, что счётчики существуют только в рамках категорий. Тогда код, приведённый ниже становится более понятным.

if( !PerformanceCounterCategory.Exists( "My Category" ) ){
CounterCreationDataCollection counters =
new CounterCreationDataCollection();

CounterCreationData avgTime = new CounterCreationData();
avgTime.CounterType = PerformanceCounterType.AverageTimer32;
avgTime.CounterName = "Average Time Of Process";
avgTime.CounterHelp = "Average Time Of Process Help String";
counters.Add( avgTime );

CounterCreationData avgTimeBase = new CounterCreationData();
avgTimeBase.CounterType = PerformanceCounterType.AverageBase;
avgTimeBase.CounterName = "Average Time Of Process Base";
counters.Add( avgTimeBase );

PerformanceCounterCategory.Create( "My Category",
"My Description", counters );
}


Что следует отметить в этом коде?
При создании категории обязательно надо проверять существует ли она уже на данной машине. Каждый счётчик описывается при помощи CounterCreationData, где указывается его тип, имя и, возможно, описание (Есть ещё ряд необязательных полей).

После заполнения коллекции CounterCreationDataCollection её можно передать в метод Create класса PerformanceCounterCategory, и требуемая категрия будет создана.

Зачем создаётся два счётчика? Вот вопрос! Дело в том, что счётчики типа Average не могут существовать отдельно. Им необходима некая база, которой является счётчик типа AverageBase. Это вполне логично, грубо говоря, для того чтобы посчитать среднее значение результата эксперимента необходимо знать не только результат каждого эксперимента, но и то, сколько раз он проводился. (Кстати, после каждого счётчика типа Average должен обязательно следовать счётчик типа AverageBase, иначе категория не создастся.)

Испробуем счётчик в действии. Для этого пишем вот что:

// сначала получим собственно счётчики
PerformanceCounter avgTime =
new PerformanceCounter(
"My Category", // имя категории
"Average Time Of Process", // именно! имя счётчика
false ); // зачем нам Readonly... будем туда писать!
PerformanceCounter avgTimeBase =
new PerformanceCounter(
"My Category",
"Average Time Of Process Base",
false );

Random rnd = new Random( 100 );

for( int i = 0; i < 100: ++i ){
// запоминаем время начала обработки
long ticks = DateTime.Now.Ticks;
// имитируем обработку со случаным временем
System.Threading.Thread.Sleep( rnd.Next( 100 ) );
// увеличиваем значение счётчика на количество тиков, прошедшее с
// момента начала обработки
avgTime.IncrementBy( DateTime.Now.Ticks - ticks );
// увеличим базу
avgTimeBase.Increment();
}

Фух... Вроде справились. Работает. Ну или должно =) Правильно работать будет только первый раз. Потом надо будет обнулять значения счётчиков. Делается это при помощи свойства RawValue.

Наверное, здесь мне стоит закончить своё повествование и отправить читателя в собственный путь. Но, позволю себе ещё кусочек кода, который лично мне кажется полезным для работы с Average счётчиками.

// полезность для работы с Average счётчиками
// создаётся из двух счётчиков, среднего и базового
public class AverageCounterWrapper{
private readonly PerformanceCounter _counter;
private readonly PerformanceCounter _counterBase;
private long _ticks = 0;

public AverageCounterWrapper( PerformanceCounter counter,
PerformanceCounter counterBase ){
_counter = counter;
_counterBase = counterBase;
}

// сброс значений счётчиков
public void Reset() {
this.Reset( 0, 0 );
}

public void Reset( long raw, long rawBase ) {
_counter.RawValue = raw;
_counterBase.RawValue = rawBase;
}

// вызвать в начале процесса
public long Mark(){
_ticks = DateTime.Now.Ticks;
return _ticks;
}

// вызвать по окончании процесса
public long Check(){
long ticks = DateTime.Now.Ticks;
this.Increment( ticks - _ticks );
return ticks;
}

public void Increment( long raw ) {
_counter.IncrementBy( raw );
_counterBase.Increment();
}

}



// полезность для работы с несколькими счётчиками одновременно
public class SimultaneousCounters{

private ArrayList _counters = new ArrayList();

public SimultaneousCounters(){
}

// добавить счётчик
public void Add( PerformanceCounter counter ) {
_counters.Add( counter );
}

// удалить счётчик
public void Delete( PerformanceCounter counter ) {
_counters.Remove( counter );
}

// сброс всех счётчиков
public void ResetAll() {
this.ResetAll( 0 );
}

public void ResetAll( long raw ) {
foreach( PerformanceCounter c in _counters ){
c.RawValue = raw;
}
}

// увеличить значение всех счётчиков
public void IncrementAll() {
foreach( PerformanceCounter c in _counters ){
c.Increment();
}
}

public void IncrementAll( long raw ) {
foreach( PerformanceCounter c in _counters ){
c.IncrementBy( raw );
}
}
}


Классы простые, но очень полезные и удобные.

(c)2005, dW

Comments: Post a Comment



<< Home

This page is powered by Blogger. Isn't yours?