ProcInsp — веб-диспетчер задач для Windows

7b4d2caab855c61605d3b15acf05bd42.png

«Сказать программисту, что уже есть библиотека, делающая Х, это то же самое, что сказать музыканту, что уже есть песня про любовь» ©

Есть разные способы посмотреть, чем занят сервер под Windows: можно зайти по RDP и открыть Task Manager или Process Explorer, а можно запустить удаленный сеанс через PowerShell и набрать команду Get-Process. Но что если серверов много и нужна информация по всем сразу? Заходить по RDP не удобно, а для работы с PowerShell требуется определенная квалификация.

Мы не нашли подходящего инструмента, поэтому разработали свой. Итак, встречайте ProcInsp — совершенно новый диспетчер задач для Windows.

Что такое ProcInsp?

Повторюсь, ProcInsp — это еще один диспетчер задач для Windows, однако в отличие от Task Manager и Process Explorer он работает через веб, к тому же собирает информацию с нескольких серверов.

ProcInsp показывает информацию о потреблении RAM и CPU на наблюдаемых серверах (в левой части экрана) и отображает запущенные на них процессы (в правой части):

5f32c649aa5b2e353a3d53bd6ea69933.png

ProcInsp интегрирован с Kibana, и при щелчке на соответствующую ссылку показывает логи выбранного процесса (в Kibana отправляется запрос, содержащий в фильтре имя хоста и идентификатор процесса). Чтобы различать множество w3wp-процессов, ProcInsp для каждого из них отображает имя пула приложений (для каждого пула IIS запускает свой w3wp-процесс).

Для CLR-процессов возможно получение более детальной информации. Если кликнуть на интересующем нас процессе, то ProcInsp выведет список потоков и их стеков вызовов. Для IIS-процессов он покажет текущие обрабатываемые веб-запросы:

08c3b08e35c2693b53844de88087bca2.png

Если внутри потока выброшено исключение, то ProcInsp отобразит его тип, сообщение и стек вызовов. При клике на ссылку Kibana откроется лог, отфильтрованный по хосту, процессу и потоку.

Вот так выглядит экран просмотра стека вызовов (обратите внимание, что ProcInsp подсвечивает точку входа в приложение, оставляя за скобками инфраструктурный код):

ea0b41ccba7d91011fae019c4a897a30.png

Почему мы решили написать ProcInsp?

Мы написали ProcInsp, поскольку не нашли удобного инструмента, позволяющего просматривать стек вызовов работающего CLR-процесса.

Конечно же, можно использовать снятие дампов и WinDbg, но это требует подключения к серверу через RDP, а главное — соответствующей квалификации специалиста: к задаче надо подключать как минимум разработчика. Что касается ProcInsp, то он позволяет «заглянуть» в стек вызовов быстрее (не надо подключаться через RDP, запускать утилиты), а использовать его может тестировщик или инженер службы поддержки.

Возможно, кто-то скажет, что информацию о потреблении ресурсов серверами и процессами необходимо смотреть через системы мониторинга (типа Prometheus). Да, это так, однако тот же Prometheus заточен под получение только числовых характеристик, поэтому через него неудобно (и даже невозможно) просматривать текущие запросы и/или потоки процессов.

Существующие утилиты для мониторинга задач (см. далее раздел «Альтернативы ProcInsp») не имеют функции просмотра информации о стеках вызовов на удаленных компьютерах. Восполнение этого пробела — еще одна причина написания ProcInsp.

Особенности реализации ProcInsp

Получение общего потребления RAM и CPU сервером

Общее использование CPU ProcInsp получает через PerformanceCounter (пакет System.Diagnostics.PerformanceCounter):

var cpuCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total", Environment.MachineName);
cpuCounter.NextValue();
System.Threading.Thread.Sleep(500); //This avoid that answer always 0
CpuUsage = (int) cpuCounter.NextValue();

Информация об общем использовании RAM вычитывается через ManagementObjectSearcher (пакет System.Management):

var wmiObject = new ManagementObjectSearcher("select * from Win32_OperatingSystem");
            
var memoryValues = wmiObject.Get().Cast()
    .Select(mo => new {
		    FreePhysicalMemory = Double.Parse(mo["FreePhysicalMemory"].ToString()),
		    TotalVisibleMemorySize = Double.Parse(mo["TotalVisibleMemorySize"].ToString())
		}).FirstOrDefault();
    
if (memoryValues != null)
{
		RamUsage = ((memoryValues.TotalVisibleMemorySize - memoryValues.FreePhysicalMemory) / memoryValues.TotalVisibleMemorySize) * 100;
}

Код получения общего потребления CPU и RAM см. на GitHub.

Получение общей информации по процессам

Чтобы получить список запущенных на компьютере процессов, можно воспользоваться функцией Process.GetProcesses(), однако в результатах будет отсутствовать информация о параметрах командной строки (а для нас это важная информация, поскольку мы хотим видеть, для какого пула приложений запущен каждый из w3wp-процессов).

Вся необходимая информация, за исключением потребления CPU и RAM, есть внутри объекта Win32_Process, который доступен через ManagementObjectSearcher (пакет System.Diagnostics.PerformanceCounter). Код получения информации по процессам см. на GitHub.

Что касается потребления CPU и RAM отдельным процессом, то эта информация есть в Win32_PerfFormattedData_PerfProc_Process, доступ к которому, опять же, получаем через ManagementObjectSearcher (см. исходный код на GitHub).

Получение списка текущих веб-запросов

В случае IIS-процессов полезной является информация о текущих обрабатываемых веб-запросах. Эта информация доступна через ServerManager пакета Microsoft.WebAdministration (см. исходный код на GitHub).

Получение перечня потоков и их стеков вызовов

Внутри объекта System.Diagnostics.Process есть свойство Threads, однако через него невозможно получить стеки вызовов других процессов (отличающихся от текущего). У Microsoft есть библиотека Microsoft.Diagnostics.Runtime (ClrMD), которая позволяет подсоединиться к запущенному процессу и получить сведения в том числе о стеках вызовов работающих потоков. Исходный код см. на GitHub.

Объем памяти, выделенной для одного потока

При разработке ProcInsp стояла задача определять объем памяти, выделенной под конкретный поток. Дело в том, что Windows выделяет память под процесс, а как эта память распределяется по потокам, не знает. Для того чтобы определить объем кучи потока, было решено написать свой калькулятор (заранее оговорюсь, что идея провалилась: калькулятор дает заниженные данные).

По идее, любое выделение памяти внутри потока приводит либо к появлению нового корневого объекта, либо к созданию ссылки из существующего корневого объекта на вновь созданный (напрямую или через иерархию объектов).

Перебрать корневые объекты, доступные из потока, можно при помощи пакета ClrMD. Далее для каждого из объектов можно получить объем памяти, а также информацию о дочерних объектах (объекты, на которые ссылается корневой объект). Для дочерних объектов, в свою очередь, доступна та же информация (объем памяти и ссылки).

Получается, при помощи обычной рекурсии, начинающейся от корневых объектов, мы теоретически получаем возможность обойти всю иерархию объектов и вычислить объем выделенной под поток памяти. Однако здесь у нас возникли сложности:

  1. Обход больших деревьев занимает слишком много времени (а иногда и вовсе приводит к зависаниям), поэтому мы ввели ограничение на глубину и ширину обхода.

  2. Некоторые объекты могут быть доступны сразу из нескольких потоков. Например, статическое поле класса будет доступно из всех потоков. Это приведет либо к тому, что поле класса будет обойдено при обходе каждого потока, либо к тому, что поле класса не будет обойдено ни разу.

Мы не стали выяснять, из-за чего все-таки не удается определить хотя бы порядок объема памяти, выделенной под поток. Возможно, этим будет интересно заняться кому-то из наших читателей :) Наша (не рабочая) реализация обхода кучи здесь.

Архитектура и точки расширения

ProcInsp отображает информацию о потреблении ресурсов на множестве серверов: клиентская часть (реализована на React и TypeScript) посылает асинхронные запросы на серверы, на которых развернуты точки входа ASP.NET Core. Клиентская и серверная части ProcInsp обмениваются друг с другом информацией через WebAPI.

c329902a27bdb954a8e1a84f5031cb42.png

При необходимости сбор информации с серверов можно осуществлять другой утилитой (реализованной, к примеру, на Java или Node.JS). С другой стороны, клиентская часть ProcInsp может собирать информацию с серверов, скажем, под управлением Linux или MacOS, если на них реализован необходимый API. Полная информация о поддерживаемых WebAPI-запросах доступна здесь.

Конфигурирование ProcInsp

Серверная часть ProcInsp в настоящий момент не конфигурируется.

Конфигурация клиентской части ProcInsp задается в файле \ClientApp\build\config.js. В настоящий момент поддерживаются следующие настройки:

  • InspServers — адреса серверов, с которых ProcInsp собирает данные.

  • IisProcs — имена процессов, которые должны отображаться при включенной опции Only IIS.

  • Kibana.Procs — адрес Kibana, по которому доступны логи для заданного процесса. Поддерживает местозаместители: ${pid}, ${machineName}, ${machineNameLowercase}.

  • Kibana.Threads — адрес Kibana, по которому доступны логи для заданного потока. Поддерживает те же местозаместители, что и Kibana.Procs, а также ${threadId}.

  • Entrypoint.Contains — строки, одна из которых должна быть в имени функции, служащей точкой входа. Данная настройка вместе со следующей задает правила, по которым ProcInsp ищет точку входа в стеке вызовов (чтобы подсветить метод, отображающий суть происходящего в приложении).

  • Entrypoint.NotContains — строки, которых не должно быть в имени функции, служащей точкой входа.

  • Requests.UrlInfo — регулярное выражение, при помощи которого ProcInsp получает основную информацию из строк запросов к IIS. Например, из www.mysite.com/Request=MyMainMethod можно получить строку MyMainMethod для отображения в списке запросов.

Актуальная информация о перечне и типах настроек доступна в файле globals.d.ts.

Ограничения

ProcInsp умеет получать подробную информацию о потоках только для CLR-процессов. Для нативных и Java-процессов отображаются лишь идентификаторы запущенных потоков и время их старта.

ClrMD умеет получать данные только по процессам, чья битность совпадает с запущенным процессом (в нашем случае — процессом ProcInsp). Это означает, что если ProcInsp запущен как 32-битное приложение, то он сможет получить данные о стеках вызовов только по 32-битным процессам. Аналогично 64-битный ProcInsp будет иметь доступ только к данным о 64-битных приложениях.

Возможный обходной путь здесь таков: запускать 2 экземпляра ProcInsp (по экземпляру на каждую битность) и присылать запросы по процессу на «правильный» экземпляр, работающий в нужной битности.

Ограничение не распространяется на отображение списка процессов: вне зависимости от того, в какой битности запущен ProcInsp, отображаются все процессы.

Текущие обрабатываемые запросы отображаются только для IIS.

Альтернативы ProcInsp

Сначала рассмотрим утилиты, позволяющие получать информацию с удаленных компьютеров.

PsList

Remote Process Explorer

Remote Process Viewer

Desktop Central

ProcInsp

Вид

Командная строка

Windows-приложение

Windows-приложение

Веб-приложение

Веб-приложение

Просмотр списка процессов

+

+

+

+

+

Инф-я о потреблении ресурсов процессом

+

+

+

+

+

Просмотр списка потоков

+

+

Просмотр стеков вызовов

+

Лицензия

Бесплатно для некоммерческого использования

Бесплатно для личного использования

Бесплатно

Платно

Бесплатно

Как видно, только ProcInsp умеет получать с удаленной машины информацию о стеках вызовов.

Однако существует ряд приложений, которые позволяют просматривать перечень потоков и их стеков вызовов на текущем компьютере. Эти утилиты имеют примерно одинаковые возможности, различаясь лишь наличием/отсутствием пользовательского интерфейса:

В интернете есть упоминания утилит Managed Stack Explorer и Clr Stack Explorer, имеющих графический интерфейс, однако первое приложение не запускается, а для второго не работают ссылки на скачивание.

WinDbg и DebugDiag также позволяют просмотреть перечень потоков и их стеков вызовов. Упоминаю их отдельно от остальных, поскольку утилиты имеют широкий круг возможностей по снятию и анализу дампов приложений и являются не диспетчерами задач, а средствами отладки.

Планы на будущее

В будущем планируется сделать ProcInsp кроссплатформенным, то есть научить его выводить информацию о процессах, запущенных на Linux и MacOS. Планируется добавить возможность просмотра подробной информации по нативным и Java-процессам, просмотр активных веб-запросов nginx и других веб-серверов, поддержку Docker. Также полезно добавить возможность управлять процессами, как минимум прерывать их и снимать дампы.

Использованные пакеты

Ссылки

© Habrahabr.ru