WPR/WPA на практике — введение в расследование производительности всей системы, когда «весь ПК тормозит»
· Го Комура · Windows, Производительность, WPR, WPA, ETW, Расследование производительности, Устранение неполадок, Разработка под Windows
«Сказали, что весь ПК стал тормозить после установки нового приложения. Но когда смотрю в Диспетчер задач, и у ЦП, и у памяти есть запас.» «Есть один ПК, который загружается 3 минуты. Понятия не имею, что не так.» — Консультации по производительности и правда часто приходят в такой форме. Общее у них то, что взгляд на конкретный процесс ответа не даёт.
Инструменты уровня процесса на месте. Доступ к файлам и реестру видно через Process Monitor, а ЦП и GC .NET-приложения можно проследить через PerfView. Но симптом вроде «весь ПК тормозит» или «ЦП простаивает, а всё равно медленно» начинается с того, что даже неизвестно, какой процесс виноват. Приложение A может тормозить из-за сканирования антивируса, или потому что другая служба тяжело пишет на диск, или из-за цепочки блокировок через несколько процессов. Нужны данные, которые записали не внутренности процесса, а ОС целиком на одной временной шкале.
Инструменты, чтобы это снять и прочитать, — Windows Performance Recorder (WPR) и Windows Performance Analyzer (WPA). WPR записывает общесистемную активность на базе ETW (Event Tracing for Windows), а WPA разбирает эту запись в графиках и таблицах. Кто использовал ЦП на каком стеке, кого ждал поток, какой процесс выдал дисковый I/O к какому файлу — факты на шаг-два глубже Диспетчера задач остаются все, с метками времени.
Рассчитанная на ИТ-сотрудников малого и среднего бизнеса и разработчиков Windows-приложений, эта статья собирает практику съёма через WPR и чтение WPA — особенно разницу между расследованием «когда ЦП высокий» и «когда ЦП низкий, а всё равно медленно» — по первичным источникам на август 2026 года.
1. Сначала вывод
- Первый выбор для расследования «весь ПК тормозит» — WPR/WPA, которые снимают и читают общесистемную ETW-трассировку. Проблемы, которые инструменты уровня процесса (Диспетчер задач, Procmon, PerfView) не могут пригвоздить, можно проследить, если смотреть каждый процесс и ядро на одной временной шкале.12
- Инструмент съёма wpr.exe входит в состав Windows 8.1 и новее. Его можно использовать без дополнительной установки. GUI-редакция (WPRUI) и анализатор WPA входят в Windows ADK.12
- Базовая процедура — три строки. От администратора:
wpr -start GeneralProfile -filemode→ воспроизвести проблему →wpr -stop C:\temp\trace.etl. Запомните только это — и можно начинать съём.3 - Полевая базовая схема — разделение «в среде заказчика только съём через wpr.exe; чтение — WPA на своей машине». Снимать можно даже на сервере, куда нельзя ставить ПО. Та же идея, что у захвата пакетов: «снять штатным инструментом, читать в Wireshark».1
- Чтение WPA начинается с классификации «ЦП, ожидание или I/O». Если ЦП горит — CPU Usage (Sampled); если ЦП простаивает, а всё равно медленно — анализ ожиданий в CPU Usage (Precise); если подозревают диск — Disk Usage: путь расходится в самом начале.45
- CPU Usage (Sampled) показывает «какая функция использовала ЦП» по выборке примерно каждые 1 миллисекунду. Разбивку «50%» из Диспетчера задач можно пройти от процесса → поток → стек → функция.6
- CPU Usage (Precise) — полная запись переключений контекста и говорит, «кого ждал поток». Пройти Waits (время ожидания), ReadyingProcess (кто разбудил) и ReadyThreadStack (стек разбудившего) — приём, который эта статья больше всего хочет передать.47
- Чтобы читать стек, нужна настройка символов. WPA по умолчанию обращается к публичному серверу символов Microsoft. Чтобы видеть имена функций в своём приложении, добавьте путь к своим PDB.8
- ETL-файл содержит внутреннюю информацию системы: имена процессов, пути файлов и тому подобное. Держите съём на минимуме необходимого и до съёма решите, как с ним обращаться, если он покинет компанию.
2. Где стоят инструменты — WPR снимает, WPA читает
Windows Performance Toolkit (WPT) — набор инструментов расследования производительности, входящий в Windows ADK (Windows Assessment and Deployment Kit); центр — пара WPR и WPA.2 Роли чётко разделены.
- WPR (Windows Performance Recorder) = съём. Он собирает группы ETW-провайдеров в единицу под названием «профиль», стартует и останавливает запись и выдаёт ETL-файл. Консольная редакция wpr.exe входит в состав Windows 8.1 и новее, без дополнительной установки. GUI-редакция (WPRUI.exe) входит в ADK.1
- WPA (Windows Performance Analyzer) = анализ. Открывает ETL-файл и разбирает его в графиках и таблицах. Требуется установка ADK.2
Иными словами, в среде заказчика ничего размещать не нужно. Снять штатным wpr.exe ОС, увезти ETL-файл и прочитать в WPA на своём ПК — то же разделение, что у захвата пакетов: «снять pktmon, читать в Wireshark».
flowchart TB
accTitle: Разделение съёма через WPR и чтения через WPA
accDescr: В среде заказчика записывайте штатным wpr.exe ОС и получите ETL-файл; увезите его и разберите в WPA, установленном через ADK на своём ПК
subgraph customer["ПК заказчика(без доп. установки)"]
wpr["wpr start → воспроизвести → stop"] --> etl["ETL-файл"]
end
subgraph office["Ваш ПК(WPA через ADK)"]
wpa["Разбор графиков и таблиц"]
end
etl -->|"Увезти"| wpa
Рис. 1: В среде заказчика записывайте штатным wpr.exe ОС и получите ETL-файл; увезите его и разберите в WPA, установленном через ADK на своём ПК.
Чем это отличается от похожих инструментов, тоже стоит собрать сначала.
| Process Monitor | PerfView | WPR + WPA | |
|---|---|---|---|
| На какой вопрос отвечает | Какой процесс что сделал с каким путём и что произошло | Как выглядят ЦП, GC и аллокации .NET-приложения | По всей ОС, куда делось время |
| Охват | Журнал операций с файлами, реестром и запуском процессов | Сначала управляемый код | Общесистемные ЦП, ожидания, диск, файловый I/O, питание и подобное |
| Подходящие симптомы | Настройка не читается, ACCESS DENIED | Медленность или память только своего .NET-приложения | Весь ПК тормозит, ЦП простаивает а всё равно медленно, виновный процесс неизвестен |
| Статья | Практическое руководство по Procmon | Практическое введение в PerfView | Эта статья |
Если Procmon — журнал операций «что сделал», а PerfView — «что произошло внутри .NET», WPA — инструмент, который аудирует «куда делось время» по всем процессам. Сама механика ETW и как инструментировать своё приложение через ETW разобраны в «Введении в журнал событий Windows и ETW». Если ваше приложение само испускает ETW-события, контрольные точки приложения записываются в ту же трассировку, и выстраивать их становится гораздо проще. Однако WPR записывает только события провайдеров, включённых выбранным профилем записи. GeneralProfile ваши провайдеры не включает, поэтому если хотите смешать их, подготовьте пользовательский профиль записи (.wprp), который включает ваши провайдеры, и комбинируйте как wpr -start GeneralProfile -start MyApp.wprp!MyAppProfile, указывая имя профиля внутри файла .wprp через !3.
3. Съём на практике (WPR) — start, воспроизвести, stop
Базовая процедура в терминале администратора.
:: List of built-in profiles you can use
wpr -profiles
:: 1. Start capture (general-purpose profile, file mode)
wpr -start GeneralProfile -filemode
:: 2. Reproduce the issue (check capture status with wpr -status)
:: 3. Stop and save (you can attach a description of the problem).
:: Create the destination folder in advance (without it, -stop fails to save)
mkdir C:\temp 2>nul
wpr -stop C:\temp\slow-pc.etl "Reproduced the issue where the whole PC becomes slow while starting App X"
:: To abandon without saving
wpr -cancel
То, что передаёте в -start, — профиль, связка ETW-провайдеров, нужных расследованию.3 Достаточно помнить те, которыми пользуетесь часто.9
| Профиль | Что записывает | Когда использовать |
|---|---|---|
GeneralProfile |
Универсальный набор: выборки ЦП, переключения контекста и дисковый I/O | Начинайте отсюда. Первый ход, когда неизвестно, что не так |
CPU |
Подробное использование ЦП | Когда уже известно, что ЦП горит |
DiskIO |
Дисковая I/O-активность | Когда подозревают диск |
FileIO |
Файловая I/O-активность | Когда хотите проследить, к какому файлу идёт доступ |
Несколько профилей можно указать сразу, выстроив -start (например wpr -start GeneralProfile -start FileIO -filemode).3
flowchart TB
accTitle: Поток съёма WPR и как выбрать режим
accDescr: Проблему, которую можно воспроизвести на месте, снимают коротко и надёжно в file mode; проблему с неизвестным моментом ждут в кольцевом буфере memory mode по умолчанию. Проблему при загрузке или входе снимают boot trace. Во всех случаях процедура start / воспроизвести / stop одна и та же
q{"Когда это происходит?"}
q -->|"На месте"| file["File mode: короткий съём"]
q -->|"Момент неизвестен"| mem["Memory mode: ждать(3.1)"]
q -->|"Загрузка или вход"| boot["Boot trace(гл. 8)"]
file --> s1["start → воспроизвести → stop"]
mem --> s1
Рис. 2: Проблему, которую можно воспроизвести на месте, снимают коротко и надёжно в file mode; проблему с неизвестным моментом ждут в кольцевом буфере memory mode по умолчанию. Проблему при загрузке или входе снимают boot trace. Во всех случаях процедура start / воспроизвести / stop одна и та же.
3.1. Memory mode и File mode — воспроизводите или ждёте
У WPR два режима назначения записи; по умолчанию Memory mode (кольцевой буфер в памяти). Это кольцевой буфер, который перезаписывает со старых событий, поэтому он подходит, чтобы оставить съём идущим, пока ждёте проблему с неизвестным моментом, и остановить, когда она случится. Добавление -filemode переключает на File mode, и всё пишется в непрерывный файл. Это не перезаписывается; единственный потолок — свободное место на диске, и файл растёт без границ.10
flowchart TB
accTitle: Как Memory mode и File mode записывают
accDescr: Memory mode пишет в кольцевой буфер в памяти; старые события перезаписываются и остаются только самые свежие, поэтому он подходит для ожидания. File mode держит всё в файле, но единственный потолок — свободное место на диске, поэтому он подходит для короткого надёжного воспроизведения
ev["События ETW"] --> ring["Memory mode: кольцевой буфер"]
ev --> filem["File mode: растить файл"]
ring -.-> use1["Ждать неизвестный момент"]
filem -.-> use2["Короткое надёжное воспроизведение"]
Рис. 3: Memory mode пишет в кольцевой буфер в памяти; старые события перезаписываются и остаются только самые свежие, поэтому он подходит для ожидания. File mode держит всё в файле, но единственный потолок — свободное место на диске, поэтому он подходит для короткого надёжного воспроизведения.
Правило большого пальца для выбора такое.
- Воспроизводится на месте → File mode. Стартуйте непосредственно перед воспроизведением, остановите сразу после и уложите съём в несколько минут
- Момент неизвестен → Ждите в Memory mode (по умолчанию). Как только случилось —
wpr -stop - Даже несколько минут GeneralProfile могут дать ETL класса сотен МБ до ГБ. Слишком большой файл может стать неразбираемым в WPA, поэтому «чем дольше снимаете, тем лучше» контрпродуктивно1011
Чтобы снимать из GUI, запустите WPRUI, выберите профиль и Logging mode и Start/Save. Официальный How-to суммирует процедуру.11 Если просите контакт на площадке заказчика снять, три команды выше можно положить в процедуру как есть.
4. Основы чтения WPA — графики, золотое правило таблиц и сужение времени
Когда открываете снятый ETL в WPA, слева Graph Explorer перечисляет миниатюры графиков по категориям вроде System Activity, Computation, Storage и Memory.12 Перетащите нужный график на вкладку Analysis справа — сверху появится график, снизу таблица. Первые три вещи, которые нужно принять, такие.
- Золотое правило таблиц — порядок столбцов задаёт группировку. У таблицы WPA две вертикальные полосы, золотая и синяя, и столбцы слева от золотой полосы иерархизируют (группируют) данные в этом порядке, а столбцы справа от синей — агрегаты.13 Расставьте Процесс → Стек — получите агрегацию стеков по процессу; Стек → Процесс — агрегацию каждого процесса, который использует тот же стек: перетаскивание столбцов, чтобы переставить их, само по себе операция анализа. Поняли этот один пункт — и любую таблицу WPA читают одинаково.
flowchart TB
accTitle: Золотое правило таблиц — две полосы и роль столбцов
accDescr: Столбцы слева от золотой полосы иерархизируют данные в этом порядке; столбцы между золотой и синей — столбцы отображения; столбцы справа от синей — агрегаты. Перетаскивание столбцов, чтобы переставить их, само по себе операция анализа
left["Слева от золотой: группировка"] --> gold["Золотая полоса"]
gold --> mid["Между полосами: отображение"]
mid --> blue["Синяя полоса"]
blue --> right["Справа от синей: агрегаты"]
left -.-> op["Перетаскивать столбцы для анализа"]
Рис. 4: Столбцы слева от золотой полосы иерархизируют данные в этом порядке; столбцы между золотой и синей — столбцы отображения; столбцы справа от синей — агрегаты. Перетаскивание столбцов, чтобы переставить их, само по себе операция анализа.
- Сузьте временной диапазон. Перетащите на графике, чтобы выбрать диапазон, затем правый щелчок и «Zoom» — агрегация переключается только на этот интервал. Расследование производительности всегда, в принципе, смотрит только на «интервал, когда проблема происходила» (глава 9).
- Настройте символы. Чтобы читать стек по имени функции, выполните Trace > Load Symbols из меню.14 По умолчанию он обращается к публичному серверу символов Microsoft (msdl.microsoft.com), поэтому собственные стеки Windows можно разрешить при наличии интернета. Чтобы видеть имена функций в своём приложении, добавьте папку PDB своего приложения в Trace > Configure Symbol Paths.8 Что такое PDB и почему его стоит всегда хранить даже для Release-сборки, собрано в «Что такое PDB (Program Database)?». Для нативных образов NGen в .NET Framework WPR при съёме генерирует NGen PDB (.ngenpdb) и кладёт их в папку рядом с трассировкой, и WPA обращается к ним автоматически.8 Это механизм только для образов NGen, и обычный JIT-код вашего .NET-приложения вне охвата. Отображение адреса JIT-кода на имя функции разрешается из JIT-событий, которые испускает CLR, поэтому когда расследуете .NET-приложение, подготовьте профиль записи (.wprp), который включает провайдеры CLR (Microsoft-Windows-DotNETRuntime и соответствующий Rundown), и скомбинируйте так же, как свои провайдеры в главе 3:
wpr -start GeneralProfile -start MyDotNet.wprp!profile-name, чтобы события CLR вошли в трассировку (какие встроенные профили предлагает ваш локальный WPR, можно проверить черезwpr -profiles). Поверх этого храните PDB, порождённые сборкой, для отображения на строки исходников и добавьте их в путь символов выше.
flowchart TB
accTitle: Разрешение символов, чтобы читать стек по имени функции
accDescr: Выполнение Trace Load Symbols разрешает саму Windows с публичного сервера символов Microsoft, а своё приложение — с PDB сборки, добавленных в путь символов. Образы NGen используют NGen PDB, которые генерирует WPR; JIT-код .NET разрешается из JIT-событий CLR в трассировке плюс PDB сборки
load["Trace > Load Symbols"] --> ms["Windows: публичные символы"]
load --> own["Своё приложение: PDB сборки"]
ms -.-> ngen["NGen: WPR .ngenpdb"]
own -.-> jit["JIT: события CLR + PDB"]
Рис. 5: Выполнение Trace Load Symbols разрешает саму Windows с публичного сервера символов Microsoft, а своё приложение — с PDB сборки, добавленных в путь символов. Образы NGen используют NGen PDB, которые генерирует WPR; JIT-код .NET разрешается из JIT-событий CLR в трассировке плюс PDB сборки.
Когда готовы, входите со следующей развилки. На этом интервале ЦП был высоким или низким? Если высоким — глава 5 (Sampled); если низким, а всё равно медленно — глава 6 (Precise).
flowchart TB
accTitle: Развилка выбора графика WPA по симптому
accDescr: Приблизьте интервал проблемы; если ЦП высокий — идите в CPU Usage Sampled; если низкий, а всё равно медленно, проверьте привязку к одному ядру / одному потоку и затем анализ ожиданий в CPU Usage Precise; если подозревают диск — Disk Usage и File IO
zoom["Приблизить интервал проблемы"] --> cpu{"ЦП на этом интервале?"}
cpu -->|"Высокий"| sampled["Гл. 5: Sampled"]
cpu -->|"Низкий, всё равно медленно"| core{"Привязка 1 ядро / 1 поток?"}
core -->|"Да"| sampled
core -->|"Нет"| precise["Гл. 6: Precise"]
cpu -->|"Подозревают диск"| disk["Гл. 7: Диск / файловый I/O"]
Рис. 6: Приблизьте интервал проблемы; если ЦП высокий — идите в CPU Usage Sampled; если низкий, а всё равно медленно, проверьте привязку к одному ядру / одному потоку и затем анализ ожиданий в CPU Usage Precise; если подозревают диск — Disk Usage и File IO.
5. Когда ЦП высокий — «кто жжёт какую функцию» через CPU Usage (Sampled)
Если ЦП прибит, смотрите CPU Usage (Sampled). Это данные выборки, которые примерно каждые 1 миллисекунду на каждом ЦП записывали «какой стек какого процесса сейчас выполняется», и доля числа выборок — разбивка времени ЦП как есть.6
flowchart TB
accTitle: Как работает CPU Usage Sampled
accDescr: Примерно каждые 1 миллисекунду записывается стек, выполняющийся на каждом ЦП, и агрегированная доля выборок — разбивка времени ЦП. Читайте от процесса к потоку, стеку и функции. Короткая активность, которая заканчивается между выборками, не появляется
tick["Прерывание примерно каждые 1 мс"] --> snap["Записать выполняющийся стек"]
snap --> agg["Доля выборок = разбивка ЦП"]
agg --> drill["Процесс → Поток → Стек"]
snap -.-> miss["Активность между выборками пропускается"]
Рис. 7: Примерно каждые 1 миллисекунду записывается стек, выполняющийся на каждом ЦП, и агрегированная доля выборок — разбивка времени ЦП. Читайте от процесса к потоку, стеку и функции. Короткая активность, которая заканчивается между выборками, не появляется.
- Из Computation в Graph Explorer положите CPU Usage (Sampled) на вкладку Analysis и выберите пресет Utilization by Process, Stack.5
- Смотрите процессы по убыванию Weight (или Count). Личность того, что в Диспетчере задач было «50%», сначала проясняется на уровне процесса.
- Разверните столбец Stack виновного процесса. Стеки агрегированы деревом, и проход по пути, где число на развилке почти не падает, приводит к функции, которая жжёт ЦП. Если символы разрешены, это прямая линия до какой функции в вашем коде.
- Если разворачивать дерево утомительно, переключите отображение графика на Flame. Рисуется с шириной = доля времени ЦП, поэтому какой путь вызова доминирует, видно сразу. У CPU Usage (Sampled) есть и пресет Flame by Process, Stack.13
Есть одна оговорка. Поскольку это выборка, короткая активность, которая заканчивается между выборками, не появляется.6 Помните это как инструмент увидеть «где ЦП использовался в агрегате», а не инструмент измерить точную длительность каждого вызова.
6. Когда ЦП низкий, а всё равно медленно — CPU Usage (Precise) и анализ ожиданий
Это ядро статьи. Прежде чем переходить к анализу ожиданий, однако, есть что подтвердить. «Общая загрузка ЦП низкая» не значит «ЦП не узкое место». На 16-ядерном ПК последовательная работа, прибитая к одному ядру (один UI-поток, который крутится в полную силу), выглядит всего примерно как 6% в целом. Сначала проверьте в Sampled главы 5 (или Utilization by CPU у CPU Usage (Precise)), что нет привязки к конкретному ядру или потоку, и если нет — идите в эту главу: работа не не может выполняться, она ждёт. Что говорит, чего она ждёт, — CPU Usage (Precise).
Где Sampled — выборка, Precise — полная запись переключений контекста (переключений потоков). Поток входит в ожидание, его кто-то будит (Ready), и он садится на ЦП — этот круговой рейс остаётся по одной строке, и можно читать следующие столбцы.74
| Столбец | Смысл |
|---|---|
| NewThreadStack | На каком стеке этот поток вошёл в ожидание (= чем занимался, когда остановился) |
| Waits (us) | Сколько ждал |
| Ready (us) | Сколько его заставляли ждать от пробуждения до посадки на ЦП (конкуренция за ЦП) |
| ReadyingProcess / ReadyingThreadId | Процесс и поток, которые разбудили этот поток (сняли ожидание) |
| ReadyThreadStack | На каком стеке разбудивший его разбудил |
flowchart TB
accTitle: Один круговой рейс ожидания и как соответствуют столбцы
accDescr: Поток входит в ожидание на стеке, который остаётся в NewThreadStack, и ждёт время Waits. Когда кто-то его будит, эта сторона остаётся в ReadyingProcess и ReadyThreadStack; он ждёт время Ready из-за конкуренции за ЦП и затем снова выполняется
run1["Выполняется"] -->|"Войти в ожидание"| waitst["Ожидание(Waits us)"]
waitst -->|"Кто-то будит"| ready["Ready(конкуренция за ЦП)"]
ready -->|"Садится на ЦП"| run2["Снова выполняется"]
waitst -.-> col["NewThreadStack / ReadyingProcess"]
Рис. 8: Поток входит в ожидание на стеке, который остаётся в NewThreadStack, и ждёт время Waits. Когда кто-то его будит, эта сторона остаётся в ReadyingProcess и ReadyThreadStack; он ждёт время Ready из-за конкуренции за ЦП и затем снова выполняется.
Паттерн чтения такой.4
- Примените пресет Utilization by Process, Thread и добавьте в столбцы NewThreadStack и ReadyThreadStack.
- Сначала идентифицируйте поток, который выполнял задержанную операцию (UI-поток, поток, обрабатывавший данный запрос). Смотреть только по убыванию суммарных Waits запутывает, потому что потоки, которые «намеренно ждут всё время», вроде насоса сообщений или таймера, занимают вершину. Когда нашли целевой поток, если его CPU Usage (ms) велик — это проблема ЦП главы 5; если доминируют Waits — проблема ожидания.
- Разверните NewThreadStack и посмотрите чем он занимался, когда остановился.
WaitForSingleObjectилиEnterCriticalSection— ожидание блокировки; внутри синхронного I/O вродеReadFile— ожидание I/O; внутри приёма сокета — ожидание ответа собеседника. - Затем посмотрите кто снял ожидание. Разверните ReadyThreadStack и проверьте ReadyingProcess / ReadyingThreadId. Если разбудили из
KiTimerExpirationядра — это был таймер (= спал до тайм-аута); если разбудили из обработки завершения I/O — это подтверждает ожидание I/O.4 - Если сторона, которая разбудила, — другой поток или другой процесс, расследуйте тот поток той же процедурой. «A ждал, пока B отпустит блокировку, B ждал RPC-ответа C, C ждал дискового I/O» — то, что у вас есть, когда прошли эту цепочку до корня, и есть критический путь задержки.7
flowchart TB
accTitle: Цепочка критического пути, которую проходите в анализе ожиданий
accDescr: В NewThreadStack задержанного потока A посмотрите, чем он занимался, когда остановился, идентифицируйте разбудившего B по ReadyThreadStack и ReadyingProcess и расследуйте B той же процедурой до корневого дискового I/O
a["Поток A(задержанная работа)"] -->|"Ожидание блокировки"| b["Поток B(держит блокировку)"]
b -->|"Ожидание RPC"| c["Процесс C"]
c -->|"Ожидание синхронного I/O"| d["Дисковый I/O(корень)"]
d -.->|"Завершение будит C"| c
c -.->|"Ответ будит B"| b
b -.->|"Отпуск блокировки будит A"| a
Рис. 9: В NewThreadStack задержанного потока A посмотрите, чем он занимался, когда остановился, идентифицируйте разбудившего B по ReadyThreadStack и ReadyingProcess и расследуйте B той же процедурой до корневого дискового I/O.
В случае вроде «сделали многопоточность, а быстрее не стало» эта процедура показывает всех рабочих, выстроившихся на одной блокировке, как есть. Как избегать конкуренции за блокировки проектом разобрано в «Практические лучшие приёмы многопоточности: издание .NET», а механизм Windows, который работает по уведомлению о завершении вместо ожидания в синхронном I/O, — в «Порты завершения I/O (IOCP) и пул потоков .NET». Пригвоздить в WPA «кого ждал» и починить это теми проектными доводами — один непрерывный поток.
7. Диск и файловый I/O — как идентифицировать «кто-то сканирует диск»
Классический виновник «весь ПК тормозит» — не ЦП, а диск. Расследуют через Disk Usage и File I/O в категории Storage.15
Disk Usage — запись дискового I/O, и важны два столбца. Disk Service Time — время, которое дисковое устройство реально потратило на обработку этого I/O; IO Time — время от входа I/O в очередь ОС до завершения. IO Time всегда не меньше Service Time на величину очереди, поэтому если IO Time намного длиннее Service Time, этот I/O «ждал в очереди».6 Одного этого, однако, недостаточно, чтобы решить, виноват ли другой процесс, сделавший очередь, или только собственный тяжёлый I/O этого процесса выстроился на медленном устройстве. Не делайте вывод здесь; закрывайте вопрос через Service Time (собственный отклик устройства) и следующую разбивку по процессу, пути и стеку.
Затем пресетом Utilization by Process, Path Name, Stack смотрите, какой процесс выдал I/O к какому файлу с какого стека, по убыванию IO Time или Size.15 Ответы, которые часто выходят в поле, — эти два.
- Антивирус сканировал каждый файл. В окне, когда приложение медленно стартовало, процесс антивируса виден выдающим большой объём чтений. Имя процесса, путь и объём — доказательство как есть для разговора об исключениях.
- Другой процесс тяжело писал. Резервное копирование, индексатор, слишком много логов и подобное. Когда запись достигает диска, задействован cache manager, поэтому тот факт, что «момент, когда вы записали» и «момент, когда диск занят» могут расходиться, также разобран в «Cache Manager — когда ваш WriteFile на самом деле достигает диска?».
File I/O — на слой выше, запись файловых операций, которые выдало приложение (Create/Read/Write и подобное), и пресеты вроде Duration by Process, Thread, Type могут агрегировать время по имени файла и по операции.15 Случай, который тратит время в файловой системе или драйвере-фильтре до достижения диска, в Disk Usage не появляется, поэтому само расхождение — «Disk Usage спокоен, а File I/O медленный» — уже подсказка. Если хотите начать с механики синхронного и асинхронного I/O, см. «Синхронный и асинхронный I/O — что на самом деле значит OVERLAPPED».
flowchart TB
accTitle: Разные слои, которые видят File IO и Disk Usage
accDescr: Файловая операция приложения идёт через файловую систему и драйверы-фильтры из очереди I/O ОС к дисковому устройству. File IO записывает операции на верхнем слое; Disk Usage записывает I/O, дошедший до диска; разница между IO Time и Disk Service Time — время очереди
app["Приложение: ReadFile / WriteFile"] --> fio["ФС и фильтры(File I/O)"]
fio --> queue["Очередь I/O ОС"]
queue --> dev["Дисковое устройство(Disk Usage)"]
fio -.-> n1["Не видно в Disk Usage"]
queue -.-> n2["IO Time − Service Time"]
dev -.-> n3["Service Time = устройство"]
Рис. 10: Файловая операция приложения идёт через файловую систему и драйверы-фильтры из очереди I/O ОС к дисковому устройству. File IO записывает операции на верхнем слое; Disk Usage записывает I/O, дошедший до диска; разница между IO Time и Disk Service Time — время очереди.
Линию мысли «может, не хватает памяти и идёт свопинг» можно сначала изолировать в Диспетчере задач и Мониторе ресурсов, прежде чем идти в WPA. Однако не снимайте её только с committed-памяти: даже при запасе commit возможна ситуация, когда давление на физическую память обрезает working set и жёсткие ошибки страниц продолжаются. Также проверьте доступную физическую память и «Hard Faults/sec» в Мониторе ресурсов. Как их читать — в «Что на самом деле значит «использование памяти» в Windows?».
8. Медленная загрузка и вход — вход в boot trace
Тип «загружается 3 минуты» заканчивается раньше, чем вы успеете вручную выполнить wpr -start. У WPR есть boot trace, и можно распорядиться, чтобы ОС автоматически начала запись при следующей загрузке.3
:: 1. Arrange automatic recording on the next boot
wpr -boottrace -addboot GeneralProfile -filemode
:: 2. Restart (reproduce the slow boot)
:: 3. After boot, stop recording and save (the arrangement is also cleared)
mkdir C:\temp 2>nul
wpr -boottrace -stopboot C:\temp\boot.etl "Issue where boot takes 3 minutes"
flowchart TB
accTitle: Поток boot trace
accDescr: После того как вы распорядились автоматической записью на следующей загрузке через addboot и перезапустили, ОС автоматически начинает запись при загрузке. Сохранение через stopboot после входа также снимает распоряжение. Чтобы отказаться, снимите его через cancelboot
add["wpr -boottrace -addboot"] --> rebootpc["Перезапуск(медленная загрузка)"]
rebootpc --> auto["ОС пишет при загрузке"]
auto --> stop2["После входа: -stopboot"]
add -.-> cancel["Отказаться: -cancelboot"]
Рис. 11: После того как вы распорядились автоматической записью на следующей загрузке через addboot и перезапустили, ОС автоматически начинает запись при загрузке. Сохранение через stopboot после входа также снимает распоряжение. Чтобы отказаться, снимите его через cancelboot.
Измерение загрузки и завершения работы, которым раньше владел xbootmgr, в текущем WPR тоже можно запускать опциями вроде -onoffscenario Boot.3 Снятую трассировку читают тем же набором, что в предыдущих главах. Смотрите, какой процесс родился когда, на временной шкале в графике Processes, приблизьте окно, где загрузка застряла, и классифицируйте ЦП, ожидание или диск — стартовое приложение, которое чего-то ждёт последовательно, старт службы, застрявший на конкретном I/O, и подобное становятся видны. Анализ загрузки — глубокая специальность сама по себе, поэтому эта статья доходит только до входа: «проблему, которую нельзя поймать вручную, всё равно можно снять через WPR». Начните с общей картины через boot trace GeneralProfile.
9. Рабочий паттерн — классифицировать → приблизить → стек, повторно
Когда инструменты ясны, вот паттерн расследования в целом.
- Пригвоздите время явления. Не «было медленно», а «10:23:40–10:24:10 было медленно». Логи приложения, журнал событий, заметка того, кто оперировал — подойдёт что угодно. Если ваше приложение пишет контрольные точки в ETW или журнал событий, события внутри трассировки становятся временными кольями как есть.
- Приблизьте только этот интервал. Агрегация всей трассировки усредняется, и аномалия, которая важна, размывается. Анализ WPA всегда — сравнение «интервала, который был аномальным» и «интервала, который был нормальным».
- Сначала классифицируйте «ЦП, ожидание или I/O». Смотрите CPU Usage (Sampled); если горит — глава 5. Если не горит — Waits в CPU Usage (Precise) (глава 6). Если IO Time в Disk Usage раздут — глава 7. Сначала взять эту развилку на три стороны не даёт заблудиться.
- Повторяйте гипотеза → приблизить → стек. Если думаете «антивирус?», сузьте до этого процесса и подкрепите стеком. Если не держится — следующая гипотеза. Не делать вывод, пока не дошли до стека и не подкрепили — дисциплина такого расследования.
flowchart TB
accTitle: Итеративный цикл расследования производительности
accDescr: Пригвоздите время явления, приблизьте интервал, классифицируйте ЦП / ожидание / I/O, сформируйте гипотезу и сузьте, подкрепите стеком. Если держится — причина подтверждена; если нет — повторите со следующей гипотезой
time["Пригвоздить время"] --> zoomstep["Приблизить этот интервал"]
zoomstep --> triage["Классифицировать ЦП / ожидание / I/O"]
triage --> hypo["Гипотеза и сужение"]
hypo --> stack["Подкрепить стеком"]
stack -->|"Держится"| fix["Причина подтверждена"]
stack -->|"Не держится"| hypo
Рис. 12: Пригвоздите время явления, приблизьте интервал, классифицируйте ЦП / ожидание / I/O, сформируйте гипотезу и сузьте, подкрепите стеком. Если держится — причина подтверждена; если нет — повторите со следующей гипотезой.
Наконец, обращение с файлом съёма. ETL-файл широко отражает внутренности системы: имена каждого процесса, пути открытых файлов, загруженные модули и (в зависимости от профиля) имена ключей реестра. Стандартный съём GeneralProfile не включает тела данных вроде содержимого обмена, но если вы включили пользовательский провайдер, полезная нагрузка этого события (строки, которые записало приложение, и подобное) входит как есть. Подтвердив, что испускают включённые вами провайдеры, относитесь к нему как к файлу достаточно конфиденциальному, чтобы покинуть компанию. Как и с захватом пакетов, встройте в процедуру минимум необходимого съёма, согласование со стороной, которой передаёте, и срок хранения и удаление.
flowchart TB
accTitle: Что отражает ETL-файл и как с ним обращаться
accDescr: ETL отражает каждое имя процесса, пути открытых файлов, модули и в зависимости от профиля имена ключей реестра; включение пользовательского провайдера также включает его полезную нагрузку. Относитесь как к конфиденциальному: минимум необходимого съёма, согласование с другой стороной, срок хранения и удаление
etl["ETL-файл"] --> a1["Имена, пути, модули"]
a1 --> a2["Ключи реестра(некоторые)"]
a2 --> a3["Пользовательская нагрузка"]
a3 -.-> rule["Относиться как к конфиденциальному"]
Рис. 13: ETL отражает каждое имя процесса, пути открытых файлов, модули и в зависимости от профиля имена ключей реестра; включение пользовательского провайдера также включает его полезную нагрузку. Относитесь как к конфиденциальному: минимум необходимого съёма, согласование с другой стороной, срок хранения и удаление.
10. Итоги
- «Весь ПК тормозит», которое Диспетчер задач не объясняет, расследуют общесистемной ETW-трассировкой — съём через WPR, чтение через WPA. wpr.exe входит в состав Windows 8.1 и новее, поэтому держится разделение: снять в среде заказчика, увезти ETL и прочитать в WPA на своей машине.
- Съём — три шага
wpr -start GeneralProfile -filemode→ воспроизвести →wpr -stop trace.etl. Если воспроизводите — File mode в пределах нескольких минут; если ждёте — Memory mode (кольцевой буфер). Дольше — не лучше. - WPA можно начинать, когда приняты три пункта: золотое правило таблиц (слева от золотой полосы = группировка), приближение временного диапазона и настройка символов (своему приложению нужны PDB).
- Если ЦП высокий, пройдите процесс → стек → функция в CPU Usage (Sampled). Если ЦП низкий, а всё равно медленно, пройдите цепочку NewThreadStack (чем занимался, когда остановился) → Waits (сколько ждал) → ReadyingProcess и ReadyThreadStack (кто разбудил) до корня в CPU Usage (Precise).
- Для диска смотрите «время в очереди» по разнице Disk Usage IO Time и Service Time и идентифицируйте причину (медленно ли само устройство или кто сделал очередь) из Service Time и разбивки по процессу, пути и стеку. Медленную загрузку можно снять через
wpr -boottrace. - Рабочий паттерн: (1) пригвоздить время (2) приблизить интервал (3) классифицировать ЦП, ожидание или I/O (4) повторять гипотеза → приблизить → стек. Относитесь к ETL как к конфиденциальному, потому что он содержит внутреннюю информацию.
Экран WPA пугает, и в первый час все теряются. Но когда две оси — «слева от золотой полосы группировка» и «Sampled — где жгло, Precise — кого ждал» — внутри, остальное — та же операция снова и снова. В следующий раз, когда придёт консультация «у ЦП есть запас, а всё равно медленно», закройте Диспетчер задач и снимите трассировку.
Похожие статьи
- Как точно найти причину «медленной работы» с PerfView и dotnet-trace — практическое введение в анализ производительности .NET
- Практическое руководство по Process Monitor (ProcMon) — как за 10 минут выяснить, почему «настройки не читаются» или возникает ACCESS DENIED
- Введение в журнал событий Windows и ETW — как использовать стандартные механизмы ОС для логов бизнес-приложения
- Что на самом деле значит «использование памяти» в Windows? — правильно читать Working Set, Private Bytes, Commit и файл подкачки
- Настройка планирования процессора в Windows — фоновые службы и P/E-ядра
- Что такое PDB (Program Database) — разбираемся в отладочной информации, символах и Source Link
Смежные области консультирования
KomuraSoft LLC занимается расследованием общесистемных проблем производительности вроде «весь ПК стал тормозить, и я не знаю почему», «у ЦП есть запас, а приложение всё равно медленное» и «только в конкретной среде крайне медленно загружается». Мы ведём как одно непрерывное поручение проект съёма через WPR/WPA (в какой среде, какой профиль, сколько снимать), разбор трассировки и вытекающее исправление на стороне приложения и на стороне настроек.
- Разработка Windows-приложений
- Исследование дефектов и анализ первопричин
- Техническая консультация и ревью проекта
- Связаться с нами
Справочные ссылки
-
Microsoft Learn, Introduction to WPR. О том, что WPR — инструмент записи производительности на базе ETW; о том, что консольная редакция WPR.exe входит в состав Windows 8.1 и новее без дополнительной установки; о её отношении к GUI-редакции WPRUI.exe; и об идее профиля записи. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Windows Performance Analyzer. О том, что WPA входит в Windows ADK, это инструмент анализа, который строит графики и таблицы данных из ETW-событий, записанных WPR, Xperf и подобными, и может открыть и разобрать любой ETL-файл. ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, WPR Command-Line Options. О синтаксисе wpr -start/-stop/-cancel/-status/-profiles; о -filemode (по умолчанию memory mode); об указании нескольких профилей сразу; о boot trace через -boottrace (addboot/stopboot/cancelboot); и о записи переходов On/Off вроде Boot через -onoffscenario. ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Microsoft Learn, CPU Analysis. Определения столбцов графика CPU Usage (Precise) (NewThreadStack, ReadyThreadStack, ReadyingProcess, Waits и подобное); процедура развернуть ReadyThreadStack и пройти ReadyingProcess/ReadyingThread до корневой причины ожидания; и как отличить пробуждение от KiTimerExpiration (ожидание таймера) или от завершения I/O. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, Troubleshoot processes and threads by using WPR and WPA. Конфигурации вроде чтения CPU Usage (Sampled) как Process→Stack при высокой загрузке ЦП и использования CPU Usage (Precise) Readying Process, Readying Thread, Readying Stack и столбца Wait в анализе ожиданий; и таблица соответствия профилей и графиков по симптому. ↩ ↩2
-
Microsoft Learn, Exercise 2 - Evaluate Fast Startup Using Windows Performance Toolkit. О том, что CPU Usage (Sampled) — выборка с интервалом около 1 миллисекунды и короткая активность между выборками не записывается; о процедуре пройти процесс → поток → стек, чтобы идентифицировать разбивку потребления ЦП; и о смысле Disk Usage IO Time (включая время очереди) и Disk Service Time (время обработки диска). ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, Exercise 3 - Understand Critical Path and Wait Analysis. Идея анализа критического пути (классификация Running / Ready / Waiting); смысл столбцов таблицы CPU Usage (Precise) NewThreadStack, ReadyThreadStack, ReadyingProcess, Waits, Ready и подобное; и процедура по очереди проходить разбудивший поток, чтобы распутать цепочку задержек. ↩ ↩2 ↩3
-
Microsoft Learn, Loading Symbols. О том, что когда _NT_SYMBOL_PATH не задан, WPA по умолчанию обращается к публичному серверу символов Microsoft (msdl.microsoft.com); о добавлении пути PDB для своих компонентов; и о том, что WPR генерирует PDB для управляемых символов .NET в папке .ngenpdb рядом с трассировкой и WPA обращается к ним автоматически. ↩ ↩2 ↩3
-
Microsoft Learn, Built-in Recording Profiles. Список встроенных профилей записи WPR (CPU usage, Disk I/O activity, File I/O activity, Registry I/O activity, Networking I/O activity и другие) и что записывает каждый профиль. ↩
-
Microsoft Learn, Logging Mode. О том, что режимы записи — File (непрерывный файл) и Memory (кольцевой буфер в памяти) и по умолчанию Memory; о том, что Memory подходит для проблемы с неизвестным моментом и старые события перезаписываются; и о том, что единственный потолок File — свободное место на диске и слишком большой файл может стать неразбираемым в WPA. ↩ ↩2
-
Microsoft Learn, WPR How-to Topics. О процедуре старта и остановки записи в WPRUI; о выборе профиля, уровня детализации и Logging mode; и об оговорке, что длинная запись может сделать файл огромным и неразбираемым в WPA, поэтому следует выбирать Memory mode. ↩ ↩2
-
Microsoft Learn, Graph Explorer. О том, что окно Graph Explorer перечисляет миниатюры графиков по категориям вроде System Activity, Computation, Storage и Memory; и о том, что график перетаскивают на вкладку Analysis, чтобы показать его вместе с таблицей. ↩
-
Microsoft Learn, Graphs (WPA Features). Отображение Flame graph в WPA; структура таблицы, в которой столбцы слева от золотой полосы — группировка, а столбцы справа от синей — агрегаты; и пресет Flame by Process, Stack у CPU Usage (Sampled). ↩ ↩2
-
Microsoft Learn, Load Symbols or Configure Symbol Paths. О загрузке символов через Load Symbols из меню Trace в WPA; и о процедуре задать и изменить путь символов в диалоге Configure Symbol Paths. ↩
-
Microsoft Learn, List of WPA Graphs. Список графиков, доступных в WPA. Пресеты Disk Usage вроде IO Time by Process, IO Type; Service Time by Process, Path Name, Stack; Utilization by Process, Path Name, Stack; и пресеты File I/O вроде Duration by Process, Thread, Type. ↩ ↩2 ↩3
Похожие статьи
Недавние статьи с теми же тегами помогут подробнее изучить близкие темы.
Приложения, которые ломаются после выхода из сна — как работают события питания Windows и как строить бизнес-приложения, которые это переживают
Открыли ноутбук — и соединения бизнес-приложения мертвы: причина в проекте, который не учитывал сон. Статья разбирает поток уведомлений W...
DllMain и блокировка загрузчика — настоящая причина, почему говорят «ничего не делай в инициализации DLL»
Почему из DllMain нельзя вызывать LoadLibrary и синхронизироваться с другими потоками. Статья по первичным источникам объясняет, как блок...
Что на самом деле значит «Не отвечает» — как Windows решает, что приложение зависло, и как проектировать приложения, которые не зависают
«Не отвечает» в Windows — механизм, в котором ОС судит, что окно не извлекало сообщение 5 секунд, и подменяет его окном-призраком. Статья...
Win32 Thread Pool API — параллелизм без создания потоков через CreateThreadpoolWork
По всему нативному коду разбросаны вызовы CreateThread? Статья разбирает Thread Pool API Win32, переработанный в Vista, — четыре объекта ...
Именованные каналы на практике — стандартный IPC Windows: от проектирования до безопасности
Практическое руководство по именованным каналам — стандартному межпроцессному взаимодействию Windows. Статья по первичным источникам разб...
Связанные темы
Эти страницы показывают тему статьи в более широком контексте услуг и решений.
Технические темы Windows
Раздел о разработке Windows, расследовании сбоев и использовании существующих активов.
Услуги по этой теме
Статья напрямую связана со следующими услугами.
Разработка приложений для Windows
Бизнес-приложения, интеграция оборудования и средства связи — от требований до разработки.
Частые вопросы
Вопросы, которые часто возникают при консультациях по теме статьи.
- Где взять WPR и WPA? Можно ли ими пользоваться в среде заказчика, где нельзя ставить ПО?
- Инструмент съёма wpr.exe (консольная редакция) входит в состав Windows 8.1 и новее, поэтому его можно использовать без дополнительной установки. GUI-редакция WPRUI и анализатор WPA (Windows Performance Analyzer) входят в Windows ADK (Windows Assessment and Deployment Kit) и требуют отдельной установки. На практике, если разделить работу как «в среде заказчика снять только ETL-файл штатным wpr.exe ОС, увезти его и разобрать в WPA на своей машине», можно расследовать общесистемную производительность даже на площадке, куда нельзя добавлять ПО.
- Почему медленно, если Диспетчер задач показывает запас по ЦП? Что видно в WPA?
- Когда загрузка ЦП низкая, а всё равно медленно, работа не «не может использовать ЦП» — она стоит «в ожидании чего-то». Типичны конкуренция за блокировки, ожидание завершения синхронного I/O и ожидание ответа другого процесса. Диспетчер задач показывает только результат, загрузку; CPU Usage (Precise) в WPA по записи каждого переключения контекста показывает, на каком стеке поток начал ждать (NewThreadStack), сколько ждал (Waits) и кто его разбудил (ReadyingProcess, ReadyThreadStack). Пройдя сторону, которая заставила ждать, можно идентифицировать «виновника медленности» вплоть до функции.
- Как долго снимать трассировку? Файл не станет огромным?
- Если проблему можно воспроизвести, базовая схема — стартовать непосредственно перед воспроизведением, остановить сразу после и уложиться в несколько минут. По умолчанию у WPR режим Memory: запись идёт в кольцевой буфер в памяти; старые события перезаписываются, поэтому он подходит, чтобы ждать проблему с неизвестным моментом. File mode с -filemode держит всё в непрерывном файле, но единственный потолок — свободное место на диске, и слишком большой файл может стать неразбираемым в WPA. Memory mode — для долгого ожидания, File mode — для короткого надёжного воспроизведения.
- Как выбирать между PerfView и WPA?
- Оба инструмента работают с ETW-трассировками, но сильные стороны разные. PerfView глубоко понимает среду выполнения .NET и силён в расследовании, специфичном для управляемых приложений: GC, аллокации, JIT. WPA подходит, чтобы читать общесистемные ЦП, диск, файловый I/O, питание и подобное по графикам и таблицам, и это первый выбор, когда «тормозит не конкретное приложение, а весь ПК», «задействовано несколько процессов» или «подозревают что-то вне приложения (антивирус, драйвер, другой процесс)». Правило большого пальца: PerfView — для медленности только своего .NET-приложения, WPR/WPA — для медленности всей системы.
- Можно ли запускать WPR в продуктивной среде заказчика?
- Короткий съём на практике обычен, но это не безусловно безопасно. ETW лёгок, но запись большого объёма событий со стеками всё же потребляет заметные ЦП и память. Соображения вроде «стартовать непосредственно перед шагом воспроизведения и остановить сразу после», «уложить съём в несколько минут» и «запускать в момент с малым влиянием на работу» нужно встроить в тот же процесс согласования, что и обычное изменение. Кроме того, ETL-файл содержит внутреннюю информацию системы — имена процессов, пути файлов, сведения об исполняемых файлах, — поэтому заранее решите, как с ним обращаться, если он покинет компанию (минимизация, срок хранения, удаление).
Об авторе
Страница с профилем автора статьи.
Го Комура
Представитель KomuraSoft LLC
Специализируется на разработке программного обеспечения для Windows, техническом консалтинге и расследовании сбоев, особенно в проектах с унаследованными системами и трудно воспроизводимыми ошибками.