Ноутбук на процессоре Intel пролежал в спящем режиме три часа и разрядился на треть, а соседний мини-ПК греется даже при закрытой крышке терминала. Обе загадки решаются одинаково: нужно заглянуть в телеметрию платформы, которую ядро Linux читает напрямую у контроллера питания и прошивки. В чипе Intel за это отвечают два посредника, PMC и P-Unit, они точно знают, кто мешает пакету засыпать, сколько энергии съел процессор за секунду и как часто он добирался до состояния C10. Задача статьи превратить эти цифры в диагноз: чтение резидентности SLP_S0 через debugfs, расчет мощности по счетчикам RAPL, замеры turbostat под нагрузкой и в простое. Сторонние сборки не понадобятся, хватит стандартного ядра, пары модулей и утилиты turbostat.

Платформенная телеметрия Intel и где ядро хранит ее данные

В каждом чипе Intel последних поколений трудится микроконтроллер PMC (Power Management Controller), собирающий показания, которые недоступны обычному коду: время пакета в состоянии C10, накопленную резидентность глубокого сна SLP_S0, заявки устройств на задержку выхода из сна по протоколу LTR. Рядом работает прошивка P-Unit, балансирующая частоты и напряжения. Драйвер intel_pmc_core публикует часть этих данных в debugfs по пути /sys/kernel/debug/pmc_core, а на мобильных Atom-платформах вроде Broxton свою телеметрию через шину IPC открывает драйвер intel_telemetry. Модули собираются опциями CONFIG_INTEL_PMC_CORE и CONFIG_INTEL_TELEMETRY, и в ядрах большинства дистрибутивов intel_pmc_core уже на месте. Верхний слой телеметрии образуют регистры RAPL (Running Average Power Limit): MSR 0x611 отдает энергию пакета, MSR 0x639 энергию ядер, MSR 0x641 энергию графики, MSR 0x619 энергию памяти. Ядро оборачивает регистры в интерфейс powercap, а turbostat читает их сама и сразу делит приращение счетчика на прошедшее время, выдавая ватты вместо сырых чисел.

Читается телеметрия тремя путями. Первый это debugfs, где intel_pmc_core держит текстовые файлы, годящиеся для cat в любых скриптах. Второй это регистры MSR, доступные через /dev/cpu/N/msr коду с правами root, и именно их читает turbostat. Третий это PMU с именем power в perf, превращающий энергетические счетчики в события профилировщика. Все три источника описывают одну кремниевую начинку под разными углами, поэтому грамотный анализ сверяет их между собой.

Подготовка системы и проверка доступа к счетчикам

Проверка начинается с трех команд:

uname -r
lsmod | grep -E "intel_pmc_core|intel_telemetry"
sudo modprobe intel_pmc_core

Если каталог /sys/kernel/debug пуст, файловую систему отладки монтируют одной строкой и сразу смотрят содержимое:

sudo mount -t debugfs none /sys/kernel/debug
ls /sys/kernel/debug/pmc_core

Для энергосчетчиков нужен доступ к регистрам MSR:

sudo modprobe msr
ls -l /dev/cpu/0/msr

Утилита turbostat поставляется вместе с исходниками ядра в каталоге tools/power/x86/turbostat, а в Ubuntu доезжает пакетом linux-tools-generic, в Fedora пакетом kernel-tools. Версия имеет значение: свежие чипы требуют свежий turbostat, иначе утилита просто не знает раскладки счетчиков и молча печатает нули. Права суперпользователя обязательны: чтение MSR и debugfs обычному пользователю закрыто, иначе любой процесс в системе смог бы приукрашивать показания потребления себе под нос. Отдельный случай это ядро без нужных модулей: если после modprobe каталог pmc_core так и не появился, чип не предоставляет интерфейс, и телеметрию придется читать только через RAPL и perf.

Резидентность SLP_S0 и пакет C10 как главный индикатор простоя

Ключевая метрика мобильной платформы это SLP_S0, состояние, при котором контроллер снимает питание почти со всех доменов SoC и оставляет живой только логику пробуждения. Разница с C10 в том, что состояние C10 описывает простой вычислительных ядер, а SLP_S0 простой всей платформы целиком. Замер занимает минуту:

cat /sys/kernel/debug/pmc_core/slp_s0_residency
sleep 60
cat /sys/kernel/debug/pmc_core/slp_s0_residency

Счетчик накапливает суммарное время глубокого сна, на большинстве систем шаг равен одной микросекунде. Если разница двух чтений составила 9 450 000, платформа провела в SLP_S0 около 9,5 секунды из 60, то есть 15,8 процента времени, и это мало: исправный ноутбук с закрытой крышкой набирает 80-90 процентов. Расчет доли глубокого сна укладывается в школьную пропорцию, где за пятиминутный интервал каждые 3 000 000 единиц счетчика равны одному проценту времени:

b=$(cat /sys/kernel/debug/pmc_core/slp_s0_residency)
sleep 300
a=$(cat /sys/kernel/debug/pmc_core/slp_s0_residency)
echo $(( (a - b) / 3000000 ))

Строка с 74 означает 74 процента сна за пять минут, значение меньше 20 дает прямой повод искать помеху. Полезно помнить, что колонки Pk%pc10 и Sys%LPI часто расходятся: ядра добрались до C10, а NVMe-накопитель или звуковая карта держат домены питания активными, поэтому LPI заметно ниже pc10. Сам разрыв между колонками указывает на устройство, а не на процессор. В ядрах постарше рядом лежит файл package_cstates_show со сводкой по состояниям пакета, его формат меняется от поколения к поколению. Файл ltr_show перечисляет устройства и заявленные ими задержки готовности: NVMe-накопитель или контроллер USB с большим значением заставляют PMC держать пакет в полном сознании даже при пустом планировщике. Запись номера устройства в соседний файл ltr_ignore запрещает контроллеру учитывать его заявку:

cat /sys/kernel/debug/pmc_core/ltr_show
echo 1 | sudo tee /sys/kernel/debug/pmc_core/ltr_ignore

Прием с игнорированием заявок считается агрессивным, устройство станет просыпаться дольше, поэтому после эксперимента систему стоит перезагрузить.

Чтение счетчиков RAPL через sysfs и перевод джоулей в ватты

Драйвер powercap выкладывает энергетику процессора в привычную файловую систему:

cat /sys/class/powercap/intel-rapl:0/name
cat /sys/class/powercap/intel-rapl:0/max_energy_range_uj
cat /sys/class/powercap/intel-rapl:0/energy_uj

Файл name печатает package-0, подкаталоги intel-rapl:0:0, intel-rapl:0:1 и intel-rapl:0:2 отвечают за домены ядер, неядерной логики и памяти, состав доменов зависит от поколения чипа, а имена всегда видны в соседних файлах name. Замер мощности укладывается в четыре строки:

a=$(cat /sys/class/powercap/intel-rapl:0/energy_uj)
sleep 10
b=$(cat /sys/class/powercap/intel-rapl:0/energy_uj)
echo $(( (b - a) / 10000000 ))

Пример расчета: пусть a равняется 4 512 900 000, b равняется 4 712 900 000, разница 200 000 000 микроджоулей, то есть 200 Дж за 10 секунд, средняя мощность 20 Вт. Делитель здесь десять миллионов, и микроджоули, набежавшие за 10 секунд, превращаются прямо в ватты. Интервалы короче 5 секунд дают шумный результат, счетчик обновляется не мгновенно, поэтому десятисекундные паузы усредняют показания лучше. Домен памяти проверяется тем же способом на файле intel-rapl:0:2: пусть за 10 секунд набежало 14 800 000 микроджоулей, деление дает 1,5 Вт, что типично для пары обычных модулей памяти. Счетчик 32-битный и после значения max_energy_range_uj начинается с нуля, поэтому при b меньше a к разнице добавляют полный диапазон. На уровне регистров шаг единицы энергии задается MSR 0x606 и на массовых процессорах равен 1/2^13 Дж, то есть около 122 микроджоулей на бит; при таком шаге 32-битный счетчик закольцовывается на 524 кДж, при среднем потреблении 30 Вт этого хватает на 4,8 часа, так что в коротких замерах перенос встречается редко.

Разбор колонок turbostat и замеры под нагрузкой и в простое

turbostat объединяет все каналы телеметрии в одну таблицу:

sudo turbostat --Summary --quiet 5

Аргумент 5 задает интервал в секундах, в свежих версиях число проходов ограничивает ключ --num_iterations 6. Колонки таблицы читаются так: Bzy_MHz показывает среднюю частоту занятых ядер, PkgTmp температуру пакета, PkgWatt мощность всего кристалла, CorWatt только вычислительные ядра, GFXWatt встроенную графику, RAMWatt память, Pk%pc10 долю времени пакета в состоянии C10, Sys%LPI долю времени в SLP_S0.

Полный вывод без ключа --Summary добавляет построчные данные по каждому ядру, колонку TSC_MHz с базовой частотой тактового генератора и счетчики IRQ и SMI. За счетчиком SMI стоит MSR 0x34, и его рост это симптом: BIOS вмешивается в управление питанием собственными перехватами, и часть времени простоя для телеметрии невидима. Два прохода дают контрастную картину. Сборка крупного проекта:

Bzy_MHz	PkgTmp	PkgWatt	CorWatt	GFXWatt	RAMWatt	Pk%pc10	Sys%LPI
3712	79	28.14	21.90	0.51	3.28	0.00	0.00

Простой на рабочем столе:

Bzy_MHz	PkgTmp	PkgWatt	CorWatt	GFXWatt	RAMWatt	Pk%pc10	Sys%LPI
1104	41	2.86	0.71	0.15	1.42	68.42	61.17

Разброс десятикратный: 28,14 Вт против 2,86 Вт, доля C10 в простое доходит до 68,4 процента, LPI до 61,2. Умножение на время превращает цифры в бюджет: 2,86 Вт на 8 часов простоя составляют 22,9 Втч, то есть почти десятая часть батареи на 240 Втч уходит, пока владелец пьет кофе. Если в простое PkgWatt держится за 10 Вт, а Pk%pc10 около нуля, пакет кто-то держит за руку, и пора идти дальше.

Атрибуция энергии через perf и сравнение базовых замеров

Точечный вопрос "сколько стоит конкретная задача" решает perf с PMU RAPL:

sudo perf stat -a -e power/energy-pkg/,power/energy-ram/ -- sleep 20

Вывод выглядит компактно:

62.41 Joules power/energy-pkg/
21.05 Joules power/energy-ram/

За 20 секунд пакет съел 62,41 Дж, это средняя мощность 3,12 Вт, память добавила 21,05 Дж или 1,05 Вт. Полный каталог событий лежит в /sys/bus/event_source/devices/power/events, там обычно встречаются energy-pkg, energy-cores, energy-gpu и energy-ram, состав зависит от поколения чипа. Теперь тот же замер вокруг кодирования видео:

sudo perf stat -a -e power/energy-pkg/ -- ffmpeg -i clip.mp4 -c:v libx264 -preset fast out.mp4

Счетчик набрал 705,2 Дж за те же 20 секунд, средняя мощность 35,3 Вт, а стоимость самой задачи равна 32,2 Вт после вычитания базовых 3,1 Вт. Список подозреваемых при лишнем расходе выстраивается по колонке Busy из полного вывода turbostat без ключа --Summary: строка ядра с высокой занятостью и приличной частотой указывает на процесс, который держит частоту и мешает пакету спать. Дальше обычный ps завершает расследование:

ps -eo pid,comm,pcpu --sort=-pcpu | head

Типичные ловушки телеметрии и порядок поиска лишнего энергопотребления

Показания врут по трем стандартным причинам: счетчики переполняются в длинных тестах, права доступа перекрыты без root, а модуль pmc_core отсутствует в собранном без него ядре. Настройки системы добавляют свою ложку: сторожевой таймер NMI будит ядра и мешает глубоким состояниям, устройства на шине USB требуют мгновенной готовности, вкладка браузера с анимацией не дает планировщику скучать. Частотный governor проверяется одной строкой, и его режим напрямую диктует потолок экономии:

cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor

У драйвера intel_pstate выбор небогатый, performance или powersave, и performance держит частоты высоко даже там, где работы нет. Независимую сверку дает батарея: файл /sys/class/power_supply/BAT0/power_now показывает суммарный расход в микроваттах, и его цифры должны ложиться рядом с PkgWatt плюс потери на преобразование питания. Если батарея отдает 18 Вт, а процессор по телеметрии съедает 3, искать следует в стороне от пакета: подсветка экрана, радиомодуль Wi-Fi и накопитель живут на своем счету. Порядок действий при подозрении на лишний расход выглядит так:

  1. снять базовый замер командой sudo turbostat --Summary 5 и записать PkgWatt, Pk%pc10 и Sys%LPI в простое;
  2. записать ноль в /proc/sys/kernel/nmi_watchdog, поскольку сторожевой таймер блокирует глубокий сон ядер;
  3. открыть /sys/kernel/debug/pmc_core/ltr_show и найти устройства с самыми большими заявленными задержками выхода;
  4. включить автосuspend шины USB параметром ядра usbcore.autosuspend=1 и убедиться, что накопитель NVMe работает с APST;
  5. повторить замер и сравнить цифры, разница между прогонами и есть цена найденных помех.

Один прогон с обнуленным таймером и прирученной шиной USB часто снижает простой пакет с 10 до 3 Вт, что за неделю работы от батареи превращается в добрые часы автономности. Регулярные замеры до и после обновления ядра ловят регрессии питания раньше, чем они дойдут до новостных лент, а таблица turbostat дает аргументы для багрепорта, который принимают всерьез.