Привет! Уже довольно продолжительное время занимаюсь метриками в windows. Процесс сбора уже отлажен, и из памяти начинают уходить детали, а поэтому пора перенести полученные знания, так скажем, на бумагу. Статья будет про то, что было, что завезли, как с этим работать, какие будут грабли и костыли к ним. Попутно затронем .net clr, asp.net, wcf, iis, signalr, etw и что-нибудь еще. Статейка для тех кто в теме, ну или почти...
На данный момент метрики в windows можно разделить на две системы: performance counters (пускай будет legacy) и event counters (модно-молодежно).
Performance counters представляет собой не просто бутерброд, а целый салат различных компонентов и кучи точек входа для работы с ними.
И как видно по схеме, все сводится в одну точку PDH, хотя опросить компоненты можно на любом уровне: будь-то advapi, registry или perflib.
Для того, чтобы выдрать данные по метрике, нужно знать ее категорию, счетчик (не путать с типом в prometheus), и (если работать через PDH) тип возвращаемого значения - берете тулзу, передаете параметры, и все вроде бы красиво, даже автоматизация какая-то есть... И вот у вас метрики летят, grafana рисует, картина ясна, но конкретики нет, потому что в инстансах вы видите такое: w3wp#101, _LM_W3SVC_1001_ROOT, --62, C400001640000001, Service@||Service.svc. Но проблески есть - в самом лучшем случае инстанс совпадает по имени с процессом. Красауцы-индусы снова что-то перемудрили. Правда, сообщество предлагает чудо лекарство от этой болезни:
Locate and then click the following registry subkey:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\PerfProc\Performance
On the Edit menu, click New , and then click DWORD Value .
Right-click New Value #1 , click Rename , and then type ProcessNameFormat to name the new value.
Right-click ProcessNameFormat , and then click Modify .
In the Data value box, type one of the following values, and then click OK :
1 : Disables PID data. This value is the default value.
2 : Enables PID data.
Right-click New Value #1 , click Rename , and then type ProcessNameFormat to name the new value. Right-click ProcessNameFormat , and then click Modify.
1 : Disables PID data. This value is the default value.
2 : Enables PID data.
После этих действий инстанс должен прекрасно читаться: PID_ProcessName. Только не было упомянуто, что распространяется фикс не на все категории метрик, к тому же это может повлиять на какие-нибудь инструменты сбора (могут перестать работать), так что все делается на свой страх и риск. Решение ну очень "ниочень".
Пробежимся по категориям и посмотрим, где и что можно подпереть...
Process
В этой категории инстанс имеет формат имени запускаемого бинаря без расширения, и, если этого бинаря много, например процесс IIS (w3wp), то к нему приклеивается порядковый номер через решетку - w3wp#101 и др. К счастью разработчики кое-что предусмотрели и завезли нам счетчик ID Process, который должен решить задачу сопоставления PID`a к инстансу метрики. Но тут есть подводные камни...
Во-первых, перечитывать соответствие надо будет постоянно (процессам свойственно перезапускаться), а если не перечитывать то инстанс может принадлежать не тому процессу, который вы ожидаете, я бы даже сказал пачка инстансов: допустим у вас выпал 100 экземпляр из 150, так вот все, что после ста, сдвинется на позицию назад, т.е. 150 станет 149 (w3wp#149).
Во-вторых, чтение конкретно этой метрики запускает сбор всего performance по процессу через NtQueryInformationProcess (пациент оказался на столе у профилировщика), а эта вещь очень дорогая и знатно так может подгрузить процессор, и пока вы будете читать, инстансы могут измениться - надо будет снова перечитывать.
В документации по PDH есть функция, которая позволяет получить все доступные метрики нужной категории без чтения значений через wildcard "(*)" (решает проблему производительности)
[
"\\\\localhost\\Process(w3wp#100)\\ID Process",
"\\\\localhost\\Process(w3wp#101)\\ID Process",
...
]
Из пути можно взять инстанс и сопоставить его с запущенным процессом, а лучше всего это делать в момент его запуска, но перед этим реактивно обновить список выше. Отслеживание перезапуска процесса с реактивностью можно реализовать с помощью классов .net для работы с ETW. Но тут выскакивает еще один момент, если бинарь ни разу не запускался в системе, то его инстанс в категории будет создан не сразу после первого запуска (это касается всех категорий, которые будут в этой статье). А поэтому не плохо бы следить еще и за изменением инстансов в категории.
ASP.NET Applications
Формат инстанса выглядит следующим образом _LM_W3SVC_1001_ROOT, где 1001 - это идентификатор сайта IIS. К этой категории, как и всем последующим, применим тот же механизм соответствия, за исключением того, что в этот раз необходимо найти не PID, а ID сайта. Способов несколько, но я напишу с чего начал и чем закончил (не все отложилось в памяти).
Для работы с IIS есть относительно неплохая либа, но не для активного использования, во время которого она плюется всевозможными исключениями , для которых нет ни описания, ни решения (смысла в профилировании я не видел), к тому же у нее долго не было апдейтов, и она перестала проходить тесты безопасности - этой поделке место на помойке.
В конце пути единственным надежным и быстрым способом сопоставить метрику к сайту оказалось чтение файла конфигурации IIS. Но не без нюансов: конфигурация вроде бы в xml, а вроде и не совсем стандарт - несколько библиотек не смогло работать с его форматом, активно ругаясь на ошибки в нем. Методом перебора выбор пал на XmlDocument. Пример...
const string section = "/configuration/system.applicationHost/sites/site"
XmlNodeList nodes = doc.DocumentElement.SelectNodes(section);
foreach (XmlNode node in nodes)
{
w3svcs.TryAdd($"_LM_W3SVC_{node.Attributes["id"].Value}_ROOT", node.Attributes["name"].Value);
}
ServiceModelService 4.0.0.0
В документации WCF есть описание формата инстанса, но как показала практика он может слегка отличаться - сейчас я вряд ли найду пример, и вам придется поверить мне на слово. Но даже если следовать описанному паттерну, то для формирования инстанса из конфига SVC нужно вычитать имя сервиса, контракт и его эндпоинт. А программисты - народ ленивый, берут чужой конфиг, добавляют свое, и на выходе получаем из одного конфига несколько сервисов. Идентификация инстанса к запущенному сервису не представляется возможным. Было принято решение слать метрики из этой категории как есть, но предоставить визуальную идентификацию на стороне grafana.
HTTP Service Url Groups
Инстанс этой категории по истине экзотический - C400001640000001. Даже предположить сложно, как это можно привязать к процессу. Но частично решение есть - прям самый жирный костыль из всего, что описано выше. Этот инстанс можно подсмотреть, выполнив команду:
netsh http show servicestate view=requestq verbose=no
Request queue name: Request queue is unnamed.
Version: 2.0
State: Active
Request queue 503 verbosity level: Basic
Max requests: 1000
Number of active processes attached: 1
Process IDs:
33800
URL groups:
URL group ID: C400001640000001
State: Active
Request queue name: Request queue is unnamed.
Number of registered URLs: 1
Registered URLs:
HTTP://+:80/health/
Server session ID: EF00001020000031
Version: 2.0
State: Active
Здесь уже есть нужная информация: URL group ID (наш инстанс) и Process IDs, по которому и можно сделать привязку к метрике.
Немного покопавшись в интернете, можно найти API для работы с URL groups, но выйти на процесс можно только через сессию (Server session ID), а его интерфейс, к сожалению, закрыт для использования.
Практика показала, что значения групп с перезапуском процесса меняется редко. И единственный, способ, который я смог применить, это банальная регулярка "@"Process\sIDs:[^\d]?(\d+).?URL\sgroup\sID:\s([a-zA-Z0-9]+)", которая применяется к выхлопу команды - не быстро и не надежно, но что есть, то есть.
SignalR
Этой категории нет по умолчанию в системе performance counters. Чтобы она появилась, нужно пнуть ее по вот этому мануалу. Но "пиналка" у меня стабильно не заработала: метрики то появлялись, то исчезали. Костыль подобрать я не смог.
Система performance counters полна загадок и тайн. Работать с ней не легко, и, к сожалению, многие новые продукты (собственно, как и сам microsoft) продолжают ее активно использовать. Статья получилась не маленькой, многие вещи могут показаться непонятными, а детали реализации не раскрытыми.
Event Counters я решил перенести в другую часть... Вопросы приветствуются!!!