Оригинал статьи Сергея Холодилова на RSDN: http://www.rsdn.ru/article/baseserv/perfcounters1.xml
VB интерпретация - Аркадий Оловянников
Общая картина Структура данных о производительности Содержимое данных о производительности База имён Удалённый доступ Параметр lpValueName функции RegQueryValueEx All together now Код |
«Счётчики производительности» (perfomance counters) – это расширяемый механизм сбора различной (в основном статистической) информации, заложенный в операционные системы линейки Windows NT, начиная с версии 3.1. Большая часть счётчиков доступна пользователю через оснастку (snap-in) Performance. О том, сколько всего интересного можно узнать, используя счётчики производительности, говорит тот факт, что примерно половина экспериментов, описанных в книге «Внутреннее устройство Windows 2000» Д.Соломона и М.Руссиновича, проводилась с использованием оснастки Performance. Кстати сказать, возможно чтение счётчиков и с удалённого компьютера. Более того, большинство API функций, отвечающих за мониторинг (Process/Module/32First/Next, GetIp/Icmp/TCP/UDP/Statistics etc.) используют именно счетчики производительности.
Общая картина |
Оглавление |
Для получения значений счётчиков производительности нужно сделать примерно такой вызов:
res = RegQueryValueEx( _ HKEY_PERFORMANCE_DATA, _ «...», _ 0, type, _ buffer, size) |
Особенности, отличающие этот код от обычного использования RegQueryValueEx:
Но главное отличие – это, конечно, возвращаемые данные.
Структура данных о производительности |
Оглавление |
Данные, возвращаемые RegQueryValueEx, имеют чёткую иерархию.
ПРИМЕЧАНИЕ В этом разделе я рассматриваю именно саму иерархию, т.е. уровни и классификацию полученных данных. Не относящиеся к делу поля описываемых структур не приводятся. Полное описание структур здесь. |
На верхнем уровне находится весь блок данных в целом. Он описывается структурой PERF_DATA_BLOCK:
Type PERF_DATA_BLOCK ' ... ' Размер всего блока данных TotalByteLength As Long ' Размер структуры. Используется как смещение до начала первого «объекта типа». HeaderLength As Long ' Количество «объектов типа», данные для которых есть в этом блоке. NumObjectTypes As Long ' ... End Type |
На следующем уровне иерархии находятся «объекты типа» (object type; по-моему, их логично было бы называть «классами» или «типами», но обычно их называют объектами; далее и я буду называть их объектами). Они бывают двух видов:
Основное логическое отличие между этими видами объектов заключается в том, что для «одиночных» объектов данные счётчиков относятся непосредственно к объекту, а для «экземплярных» данные относятся к экземпляру.
Объект описывается следующей структурой:
Type PERF_OBJECT_TYPE ' Суммарный размер этого объекта, всех его счётчиков, экземпляров и их ' данных. Используется как смещение до следующего объекта. TotalByteLength As Long ' Размер этой структуры и следующих за ней определений счётчиков. В случае ' «одиночного» объекта это смещение до начала данных, в случае ' «экземплярного» объекта – смещение до определения первого экземпляра. DefinitionLength As Long ' Размер этой структуры (смещение до начала определения счётчиков). HeaderLength As Long ' ... ' Количество счётчиков NumCounters As Long ' ... ' Количество экземпляров. Признак «одиночного» объекта – значение ' PERF_NO_INSTANCES (равное -1). NumInstances As Long End Type |
Далее идут «определения счётчиков». Их должно быть PERF_OBJECT_TYPE.NumCounters штук. Каждый счётчик представлен так:
Type PERF_COUNTER_DEFINITION ' Размер этого определения счётчика. Т.е. смещение до следующего. ByteLength As Long ' ... ' Смещение данных счётчика от начала блока данных объекта (в случае ' «одиночного» объекта) или блока данных экземпляра (в случае ' «экземплярного» объекта). CounterOffset As Long End Type |
После определений счётчиков у «одиночных» объектов находится блок данных. Он состоит из структуры PERF_COUNTER_BLOCK, и собственно значений счётчиков. Структура PERF_COUNTER_BLOCK:
Type PERF_COUNTER_BLOCK ' Размер структуры, включая следующие за ней значения счётчиков. ByteLength As Long End Type |
Данные счётчика находятся по смещению PERF_COUNTER_BLOCK.CounterOffset от начала этой структуры.
У «экземплярных» объектов всё несколько сложнее. За определениями счетчиков следуют «определения экземпляров», каждый со своим блоком данных. Блок данных имеет такую же структуру, что и блоки данных «одиночных» объектов. Определение экземпляра:
Type PERF_INSTANCE_DEFINITION ByteLength As Long ' Размер этой структуры, т.е. смещение от её ' начала до блока данных этого экземпляра. ' ... End Type |
Всё вместе выглядит примерно так:
Рисунок 1. Весь блок данных в целом.
Рисунок 2. «Одиночный» объект.
Рисунок 3. «Экземплярный» объект.
Содержимое данных о производительности |
Оглавление |
А теперь рассмотрим те же самые структуры, но с другой точки зрения.
ПРЕДУПРЕЖДЕНИЕ Некоторые поля я не описываю и даже не привожу, так как структуры и без того большие, а эти поля не кажутся мне хоть сколько-нибудь важными. Полное описание структур здесь. |
Type PERF_DATA_BLOCK Signature(7) As Byte 'Сигнатура «PERF» в Unicode ' ... Version As Long ' Версия, в Windows 2000/XP - 1 Revision As Long ' Ревизия, в Windows 2000/XP - 1 TotalByteLength As Long ' См. в предыдущем разделе HeaderLength As Long ' См. в предыдущем разделе NumObjectTypes As Long ' См. в предыдущем разделе ' ... SystemTime As SYSTEMTIME ' Системное время на момент получения данных ' ... SystemNameLength As Long ' Длина имени системы (в байтах) SystemNameOffset As Long ' Смещение имени системы от начала структуры; ' имя хранится в Unicode End Type |
Кроме имени системы здесь, пожалуй, ничего полезного нет. А имя системы совпадает с именем компьютера, с которого были получены данные, поэтому большого интереса также не представляет. Остальные структуры содержат несколько больше полезной информации.
Объекты:
Type PERF_OBJECT_TYPE TotalByteLength As Long ' См. в предыдущем разделе DefinitionLength As Long ' См. в предыдущем разделе HeaderLength As Long ' См. в предыдущем разделе ObjectNameTitleIndex As Long ' Индекс имени объекта в базе имён. ' ... ObjectHelpTitleIndex As Long ' Индекс описания объекта в базе имён. ' ... NumCounters As Long ' См. в предыдущем разделе ' ... NumInstances As Long ' См. в предыдущем разделе CodePage As Long ' Кодовая страница. См. примечание ' к PERF_INSTANCE_DEFINITION ' ... End Type |
Базе имён посвящён следующий раздел. Если кратко, то поля ObjectNameTitleIndex и ObjectHelpTitleIndex позволяют получить текстовое имя объекта и его описание. Описание может оказаться полезным только в том случае, если планируется его выводить, а вот имя можно использовать при поиске нужного объекта (обычно, правда, поступают наоборот – сначала по имени получают индекс, а уже по индексу находят объект).
Счетчики:
Type PERF_COUNTER_DEFINITION ByteLength As Long ' См. в предыдущем разделе CounterNameTitleIndex As Long ' Индекс имени счётчика в базе имён. ' ... CounterHelpTitleIndex As Long ' Индекс описания счётчика в базе имён. ' ... CounterType As Long ' Тип счётчика. Подробнее ниже. CounterSize As Long ' В некоторых случаях (зависит от типа) – ' размер данных счётчика. CounterOffset As Long ' См. в предыдущем разделе End Type |
Самое интересное поле – это, конечно, CounterType. Оно определяет тип информации, содержащейся в счётчике, размер (или механизм вычисления размера) данных счётчика, манипуляции, которые необходимо произвести со значением счётчика перед использованием, и даже форму, в которой его нужно выводить.
В MSDN это поле уклончиво описано как комбинация различных значений (values). Именно значений, а не флагов, так как некоторые из битов этого поля являются флагами, а некоторые – нет. Те, которые «нет», отличаются тем, что логически они исключают друг друга, а их битовые маски пересекаются, поэтому проверять присутствие/отсутствие оператором «&» не получается. Схема такова (в связи с отсутствием в VB понятия union а также невозможностью указать длину переменной в битах, структура приводится в С нотации):
union CounterTypeMask { DWORD dw; struct { unsigned int reserved1 : 8; unsigned int size : 2; // размер данных счётчика unsigned int type : 2; // тип счётчика unsigned int reserved2 : 4; unsigned int subtype : 4; // подтип счётчика unsigned int time : 2; // подтипы таймеров unsigned int calc : 6; // механизмы вычисления итогового значения, флаги unsigned int display : 4; // как отображать }; }; |
Я приведу только значения, соответствующие полям size, type, и два значения поля subtype. Остальные менее интересны и более запутанны (некоторые – значительно более запутанны), если хотите, можете посмотреть их в MSDN.
Константа | Значение | Описание |
---|---|---|
PERF_SIZE_DWORD | 0x00000000 | Данные - это DWORD, занимают 4 байта. |
PERF_SIZE_LARGE | 0x00000100 | Данные - это LARGE_INTEGER, занимают 8 байтов. |
PERF_SIZE_ZERO | 0x00000200 | Данных нет, длина - 0 байт. |
PERF_SIZE_VARIABLE_LEN | 0x00000300 | Размер данных определяется полем CounterSize. |
Табл.1.Поле size определяет метод определения размера данных счётчика.
Константа | Значение | Описание |
---|---|---|
PERF_TYPE_NUMBER | 0x00000000 | Число . |
PERF_TYPE_COUNTER | 0x00000400 | Счётчик чего-либо. |
PERF_TYPE_TEXT | 0x00000800 | Текст. |
PERF_TYPE_ZERO | 0x00000С00 | Всегда 0. |
Табл.2. Поле type определяет интерпретацию данных счётчика.
Константа | Значение | Описание |
---|---|---|
PERF_TEXT_UNICODE | 0x00000000 | Строчка в Unicode. |
PERF_TEXT_ASCII | 0x00010000 | Строчка в ASCII. |
Табл.3. Поле subtype определяет подробности интерпретации данных счётчика. Приведены значения только для типа PERF_TYPE_TEXT.
Полноценный анализ поля CounterType в общем виде довольно сложен. Но если вы не собираетесь писать свою оснастку Performance (да ещё и используя исключительно RegQueryValueEx…), то анализ этого поля вам нужно будет произвести только один раз, на этапе разработки приложения. В приведенных примерах анализом занимается функция TranslateCounter формы.
ПРИМЕЧАНИЕ В MSDN описываются не столько флаги/значения (хотя и они тоже), сколько разумные (с точки зрения Microsoft) комбинации. Для каждого приведена формула, позволяющая из «сырых» данных получить понятное пользователю значение. Значения VB констант приведены здесь. |
Экземпляры:
Type PERF_INSTANCE_DEFINITION ByteLength As Long ' См. в предыдущем разделе ParentObjectTitleIndex As Long ' Если у этого объекта есть «родитель», ' то это индекс имени объекта ' родителя в базе имён. ParentObjectInstance As Long ' Номер экземпляра родителя. UniqueID As Long ' Идентификатор экземпляра или PERF_NO_UNIQUE_ID. NameOffset As Long ' Смещение до начала имени экземпляра NameLength As Long ' Длина (в байтах) имени экземпляра, 0, ' если имени нет. End Type |
Поля ParentObjectTitleIndex и ParentObjectInstance позволяют установить отношения «родитель» - «ребёнок» для экземпляров. У экземпляров большинства объектов нет родителей, но, например, для потока (экземпляр объекта «Thread») родителем является его процесс (экземпляр объекта «Process»); для экземпляра объекта «Image» (образ исполняемого модуля) родитель – соответствующий экземпляр объекта «Process Address Space».
Поля UniqueID, NameOffset и NameLength позволяют определить имя экземпляра. Если UniqueID не равно PERF_NO_UNIQUE_ID (определено как -1), то экземпляры именуются по идентификаторам, и UniqueID – это как раз идентификатор. Иначе экземпляры именуются по именам, смещение имени от начала структуры указано в NameOffset, а длина имени – в NameLength.
ПРИМЕЧАНИЕ И в MSDN, и в заголовочном файле winperf.h о поле NameOffset написано: Offset from the beginning of this structure to the Unicode name of this instance. Однако о поле CodePage структуры PERF_OBJECT_TYPE и там, и там написано примерно следующее: Specifies the code page. This member is zero if the instance strings are in Unicode. Otherwise, this member is the code-page identifier of the instance names. Я ни разу не встретил ни ненулевого CodePage, ни неюникодного имени. Поэтому где правда – не знаю. |
База имён |
Оглавление |
База имён – это таблица соответствия между индексом имени/описания чего-либо и самим именем/описанием. Точнее, эти базы могут существовать для нескольких языков, причём по две для каждого – одна для имён, вторая для описаний. Хранится всё, как водится, в реестре, большими, слегка структурированными кусками данных... По этому поводу можно только слегка опечалиться.
И база имён, и база описаний находятся в значениях одного ключа реестра. Есть два варианта (они оба работают, вы можете выбирать любой, но если планируется работать под Windows NT 4.0, то посмотрите Q237304 и выберите первый):
Во всех случаях «XYZ» это трёхсимвольная строчка, содержащая «основную часть идентификатора языка» (primary language identifier) в шестнадцатеричном виде. Языком может быть либо английский, либо язык версии (не локализации, а именно версии; локализацию можно поменять из настроек Regional Options, поменять версию гораздо сложнее, я, например, не умею) Windows. Для английского «XYZ» будет «009», для русского «019». При использовании «009» строчки будут английскими, при использовании языка версии могут быть либо локализованными (если создатель соответствующего объекта позаботился об этом, подробнее во второй части статьи), либо английскими.
СОВЕТ Есть API-функция, которая вроде бы подходит для определения языка версии Windows. Это GetSystemDefaultUILanguage, но она существует только начиная с Windows 2000, да и название у неё подозрительное :)… Для Windows NT 4.0 нужно либо лезть в реестр (куда не скажу, так как не знаю), либо брать язык из VersionInfo какой-нибудь системной dll (смотрите функцию GetFileVersionInfo и её друзей). Для удалённого компьютера единственный известный мне вариант – реестр. В Windows 2000 и XP нужно смотреть ключ «HKLM\System\CurrentControlSet\Control\Nls\Language», параметр InstallLanguage. Про Windows NT 4.0 – не знаю. |
Базы хранятся в значениях типа REG_MULTI_SZ, то есть состоят из большого количества разделённых нулями строк, последняя строка завершается двойным нулём. Формат такой:
Индекс_1 Текст_1 Индекс_2 Текст_2 ... |
Здесь Индекс_X – строчка с десятичным представлением числа. Последовательные индексы совершенно точно не являются последовательными числами потому, что в базе имён все индексы (кроме первого) чётные, а в базе описаний – нечётные. Но это не значит, что они идут друг за другом через один – между ними могут быть дырки, в общем случае последовательность не обязательно везде возрастающая (скорее всего она будет возрастающей, но это никем не гарантируется).
ПРИМЕЧАНИЕ Во многих случаях индекс имени чего-либо и индекс соответствующего этому имени описания являются последовательными числами. Возникает соблазн предположить, что это закон. Однако изучение счётчиков объекта «Browser» опровергает такое предположение. |
В дополнение к этому, в параметрах «Last Counter» и «Last Help» (тип REG_DWORD) ключа «HKLM\Software\Microsoft\Windows NT\CurrentVersion\Perflib» хранятся максимальные значения индексов, использованных в базах имён и описаний соответственно.
Удалённый доступ |
Оглавление |
Поскольку всё хранится в реестре, для получения информации с удалённого компьютера (работающего под управлением Windows NT/2000/XP) можно использовать функцию RegConnectRegistry.
RegConnectRegistry( ByVal lpMachineName As String, ByVal hKey As Long, ByRef phkResult As Long) As Long |
Параметры:
Параметр | Значение |
---|---|
lpMachineName | Имя машины. В MSDN написано, что формат имени – «\\name», но просто «name» у меня тоже работало. |
hKey | Запрашиваемый описатель. Либо HKEY_LOCAL_MACHINE, либо HKEY_USERS, либо HKEY_PERFORMANCE_DATA. |
phkResult | Описатель, эквивалентный запрашиваемому, но для удалённой машины. |
Вот так просто… всё было бы, если бы все пользователи были администраторами на целевой машине… Иногда это не так. В этом случае может помочь Q155363 (HOWTO: Regulate Network Access to the Windows NT Registry), Q146906 (HOWTO: How To Secure Performance Data in Windows NT) и несколько моих дополнений:
Параметр lpValueName функции RegQueryValueEx |
Оглавление |
Здесь я просто приведу табличку из MSDN. Естественно, с некоторыми комментариями, половина из которых тоже взята из MSDN :)
ПРИМЕЧАНИЕ Во всех случаях, кроме последнего, данные относятся к тому же компьютеру, что и используемый описатель ключа реестра. То есть, если использовать HKEY_PERFORMANCE_DATA, это будет машина, на которой выполняется программа, а если передавать в RegQueryValueEx результат RegConnectRegistry – машина с которой установлено соединение. |
Значение | Описание |
---|---|
Global | Будут возвращены данные для всех объектов, кроме тех, которые относятся к категории «Costly». |
Costly | Будут возвращены данные для тех объектов, которые «долго собирать». Процесс может занять секунд пять-десять-пятнадцать. Некоторые объекты включаются как в категорию Global, так и в категорию Costly. |
xx yy zz (Разделённые пробелами десятичные представления индексов имён запрашиваемых объектов.) | Функция вернёт данные для запрошённых объектов. В некоторых случаях она по собственной инициативе вернёт данные для ещё нескольких объектов. Например, если вы хотите узнать о процессах, то получите процессы, потоки и «Job Object Details» (в Windows 2000). Если же спросить о потоках или «Job Object Details», то получите ту же тройку объектов.Запрашиваемые объекты могут относиться к любой категории. |
Foreign <имя системы> [xx yy zz] | Ещё один механизм, предназначенный для получения значений счётчиков с удалённого компьютера. О параметре <имя системы> в MSDN написано следующее: «the name of a foreign computer, such as a Novell NetWare server or a UNIX system». Наверное, это правда. Но проверить я не смог, так как под рукой не оказалось ни одного такого сервера. С Windows-серверами этот механизм у меня не заработал. Если параметры xx yy zz не указаны, то будут возращены все счётчики, если указаны, то аналогично предыдущему пункту. |
ПРИМЕЧАНИЕ Строчки «Global» и «Costly» регистро-зависимы. |
All together now |
Оглавление |
Итак, вы хотите получить значение счётчика «Counter» объекта «Object» c компьютера «Computer». Последовательность действий:
ПРИМЕЧАНИЕ Проще всего читать базу «009», так как она точно есть. Но тогда имя объекта и счётчика должны быть английскими. |
ПРИМЕЧАНИЕ Для стандартных объектов и их счётчиков индексы не изменяются от системы к системе. Поэтому для них этап получения индексов можно пропустить. Единственная проблема – я не нашёл в MSDN упоминания об этом факте… |
После получения данных о счетчиках (чтение базы имён в данном случае не считается) ключ HKEY_PERFORMANCE_DATA нужно закрыть. Но желательно делать это пореже. В том смысле, что лучше сделать это один раз в конце, а не каждый раз после обращения к RegQueryValueEx. Причина проста: для получения данных RegQueryValueEx загружает зарегистрированные в реестре dll-и. При закрытии HKEY_PERFORMANCE_DATA они могут быть выгружены, и при следующем запросе их придётся загружать заново.
Код |
Оглавление |
Для демонстрации возможностей работы с HKEY_PERFORMANCE_DATA и мониторинга различных параметров я написал несколько примеров: