Сервер или рабочая станция с современным процессором в пике способна брать 150-250 ватт, и часто такая мощность нужна системе лишь на короткие секунды, а не часами. Отсюда гудящие вентиляторы, горячий корпус и счета за электричество, которые можно сократить без потери отзывчивости. Режим power capping ставит железу потолок в ваттах: процессору запрещается расходовать больше энергии, чем разрешено, и он сам опускает частоту, чтобы уложиться в норму. В Linux это делается штатными средствами ядра, без сторонних утилит, через подсистему powercap и интерфейс sysfs. Из статьи читатель узнает, как посмотреть зоны процессора, задать долгосрочный и краткосрочный лимиты, измерить фактическое потребление и закрепить настройки после перезагрузки.

Технология RAPL и подсистема powercap внутри ядра

Аббревиатура RAPL расшифровывается как Running Average Power Limit, технология усредненного контроля мощности, которую Intel встраивает в процессоры начиная с поколений Sandy Bridge и Ivy Bridge. Смысл в том, что процессор сам отслеживает израсходованную энергию в скользящем окне времени и, едва среднее приближается к лимиту, снижает рабочие частоты. Ядро Linux подхватывает эти возможности драйвером intel_rapl и выносит их наружу через фреймворк powercap, который появился в ядрах линейки 3.13. За него отвечают опции сборки CONFIG_POWERCAP и CONFIG_INTEL_RAPL, в конфигурациях дистрибутивов они типично включены.

Фреймворк строит дерево объектов. В корне стоят типы управления, control types: intel-rapl для лимитов мощности и idle-injection для принудительных пауз, когда ядро вставляет в работу процессора простои ради экономии. Под типом живут power zones, зоны питания, и каждая описывает свою часть системы. У пакета процессора есть собственная зона и подзоны core, uncore, а на многих платформах еще и dram. Ограничение, заданное в зоне пакета, распространяется на весь процессор целиком, подзонами же регулируют распределение бюджета внутри него.

От программных ограничителей частоты, которыми управляет подсистема cpufreq, потолок RAPL отличается тем, что контроль выполняет само железо: governor лишь выбирает частоты, а технология физически не даст пакету взять больше энергии, чем записано в регистрах. Потому лимит держится даже при сбоях пользовательского пространства и одинаков для любых задач, включая контейнеры и виртуальные машины. Виртуализация это ценит: хостер ограничивает неугомонного соседа не процентами процессорного времени, а ваттами, и температура серверной перестает скакать от чужой нагрузки.

Дерево sysfs с зонами, лимитами и счетчиком энергии

Всю иерархию фреймворк публикует в sysfs, физически это /sys/devices/virtual/powercap, а удобнее путь через символьную ссылку /sys/class/power_cap. Знакомство начинают с перечисления типов и зон:

ls /sys/class/power_cap/
ls /sys/class/power_cap/intel-rapl/
cat /sys/class/power_cap/intel-rapl/intel-rapl:0/name

На однопроцессорной машине под intel-rapl лежит одна зона intel-rapl:0, на плате с двумя процессорами добавится intel-rapl:1, а внутри каждого пакета найдутся подзоны intel-rapl:0:0 и intel-rapl:0:1. Имена зон подскажет атрибут name: у пакета он обычно равен package-0, у подзон core и uncore, у зоны памяти dram, у части десктопных кристаллов встречается и подзона gpu для встроенной графики. Если каталог /sys/class/power_cap отсутствует или пуст, драйвер не загружен, его подгружают командой modprobe intel_rapl, а сообщения загрузки показывает dmesg | grep rapl. На свежих ядрах схожий интерфейс находят и владельцы части процессоров AMD, там появляется собственный тип управления с теми же атрибутами.

Счетчик израсходованной энергии хранится в energy_uj и измеряется в микроджоулях, а правила лимитов описывают атрибуты constraint_N_*, где N это номер ограничения от нуля до двух. Назначение каждого ограничения раскрывают файлы имен:

cat /sys/class/power_cap/intel-rapl/intel-rapl:0/constraint_0_name
cat /sys/class/power_cap/intel-rapl/intel-rapl:0/constraint_1_name

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

long_term
short_term

Третий номер на части процессоров называется peak_power и задает мгновенный потолок, для которого окно времени не применяется. Мгновенного значения мощности атрибут power_uj в зоне RAPL не отдает, технология выдает только накопленную энергию, поэтому потребление всегда считают по разнице двух замеров.

Подзоны несут собственные файлы constraint_0_power_limit_uw и constraint_0_time_window_us: у intel-rapl:0:0 они регулируют вычислительные ядра, у intel-rapl:0:1 блок uncore, то есть кэш, контроллер памяти и встроенную графику. Лимит пакета при этом никуда не исчезает, именно он сдерживает суммарное потребление кристалла, а подзонные значения лишь делят общий бюджет между частями. Атрибут enabled управляет контролем целиком: запись 0 в /sys/class/power_cap/intel-rapl/intel-rapl:0/enabled отключает лимиты зоны, обратная запись возвращает их в строй.

Лимиты long term и short term в ваттах и окнах времени

Долгосрочный лимит long term, он же PL1 в терминах Intel, определяет среднюю мощность, которую процессор может держать бесконечно долго. Краткосрочный short term, он же PL2, разрешает кратковременный разгон выше потолка на время, ограниченное окном time_window_us. Оба значения хранятся в микроваттах, окно в микросекундах: чтобы получить ватты и секунды, числа делят на миллион.

Текущие установки читаются напрямую:

cat /sys/class/power_cap/intel-rapl/intel-rapl:0/constraint_0_power_limit_uw
cat /sys/class/power_cap/intel-rapl/intel-rapl:0/constraint_0_time_window_us
cat /sys/class/power_cap/intel-rapl/intel-rapl:0/constraint_1_power_limit_uw

Допустим, первая команда вернула 45000000, значит пакету разрешено 45 ватт средней мощности, а окно 8000000 микросекунд означает 8 секунд. Перед изменением имеет смысл сохранить заводские значения в отдельный файл, потому что исходные лимиты sysfs нигде не дублирует и после перезагрузки вернет их из прошивки.

Пишет в эти файлы только root. Пример связки 45 ватт долгосрочно и 65 ватт в пике с окном 8 секунд:

echo 45000000 > /sys/class/power_cap/intel-rapl/intel-rapl:0/constraint_0_power_limit_uw
echo 8000000 > /sys/class/power_cap/intel-rapl/intel-rapl:0/constraint_0_time_window_us
echo 65000000 > /sys/class/power_cap/intel-rapl/intel-rapl:0/constraint_1_power_limit_uw

Драйвер не даст выйти за аппаратные границы, их подсказывают файлы constraint_0_min_power_uw, constraint_0_max_power_uw, constraint_0_min_time_window_us и constraint_0_max_time_window_us. Значение сверх максимума ядро обычно обрезает до допустимого, поэтому после записи стоит перечитать файл и убедиться, что сохранилось именно задуманное число.

Связка потолка мощности и частот под нагрузкой

Механика выглядит так. Пока скользящее среднее потребления ниже лимита, процессор ведет себя как обычно и поднимает частоты до заявленного турбо. Едва среднее упирается в long term, управление перенимает сама технология: множители частоты снижаются до тех пор, пока потребление не уложится в ватты. Краткосрочный лимит дает запас свободы на несколько секунд, поэтому короткие пики приложений ощущаются как раньше, а длительная нагрузка выходит на устойчивую, более скромную частоту.

Проверяют связку синтетической нагрузкой и наблюдением за частотами. Нагрузку запускают в одном терминале, наблюдение во втором:

stress-ng --cpu 0 --cpu-method matrixprod --timeout 60s
sudo turbostat --quiet --interval 5

В колонках Bzy_MHz и PkgWatt видна связка цифр: при 45-ваттном потолке процессор с базовой частотой 3.0 ГГц и турбо до 4.5 ГГц под полной нагрузкой всех ядер обычно стабилизируется в районе 2.3-2.8 ГГц, точные значения зависят от поколения и качества охлаждения. Без лимита та же машина в коротком пике способна брать и 120 ватт. Если среднее потребление заметно ниже заданного лимита, а частоты все равно низкие, дело в температурных ограничениях, которые powercap не контролирует.

Измерение фактического потребления через счетчик energy_uj

Счетчик energy_uj копит израсходованную энергию с момента загрузки, диапазон его значений ограничен числом max_energy_range_uj, по достижении которого отсчет начинается заново. Мощность вычисляют по двум замерам, разницу показаний делят на разницу времени:

a=$(cat /sys/class/power_cap/intel-rapl/intel-rapl:0/energy_uj); t1=$(date +%s.%N)
sleep 10
b=$(cat /sys/class/power_cap/intel-rapl/intel-rapl:0/energy_uj); t2=$(date +%s.%N)
awk -v a=$a -v b=$b -v t1=$t1 -v t2=$t2 'BEGIN{print (b-a)/(t2-t1)/1000000, "W"}'

Если за 10 секунд счетчик вырос на 399720000, деление даст 39.97 ватта, то есть система в среднем брала около 40 ватт. Когда второй замер оказался меньше первого, счетчик обнулился, и к разнице прибавляют max_energy_range_uj. Переполнение видно на конкретных числах: первый замер дал 850000000, второй 400000000, а диапазон max_energy_range_uj равен 900000000. Разница отрицательная, значит счетчик прошел через ноль, к разнице прибавляют диапазон и получают 450000000 микроджоулей, то есть 450 джоулей за 10 секунд или около 45 ватт. Схема с двумя замерами годится и для скриптов мониторинга, где показания снимают раз в минуту и пишут в базу. Запись 0 в energy_uj сбрасывает счетчик, но на части процессоров атрибут доступен только для чтения, тогда замер выполняют без сброса. Отдельный счетчик есть у зоны dram, по нему видно и потребление памяти, а готовые цифры в ваттах печатает turbostat в колонке PkgWatt.

Сохранение лимита после перезагрузки через юнит systemd

Записанные в sysfs значения живут до перезагрузки: при старте системы драйвер заново читает заводские лимиты из прошивки. Чтобы ограничение применялось само, пишут юнит systemd с типом oneshot:

[Unit]
Description=Apply CPU power limits
After=multi-user.target

[Service]
Type=oneshot
ExecStart=/bin/sh -c 'echo 45000000 > /sys/class/power_cap/intel-rapl/intel-rapl:0/constraint_0_power_limit_uw'
ExecStart=/bin/sh -c 'echo 8000000 > /sys/class/power_cap/intel-rapl/intel-rapl:0/constraint_0_time_window_us'
ExecStart=/bin/sh -c 'echo 65000000 > /sys/class/power_cap/intel-rapl/intel-rapl:0/constraint_1_power_limit_uw'
RemainAfterExit=yes

[Install]
WantedBy=multi-user.target

Файл сохраняют как /etc/systemd/system/cpu-power-limit.service, затем обновляют конфигурацию и включают юнит:

systemctl daemon-reload
systemctl enable --now cpu-power-limit.service

После каждой загрузки служба отработает один раз и выставит лимиты. Состояние проверяют командой systemctl status cpu-power-limit.service либо чтением constraint_0_power_limit_uw, а при смене значений правки вносят в строки ExecStart юнита и повторяют daemon-reload. Сюда же удобно добавить лимиты подзон, чтобы распределить бюджет мощности внутри пакета по своему усмотрению.

Типичные ошибки и границы применимости лимитов

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

  1. запись без прав root завершается строкой Permission denied, каталоги sysfs этой подсистемы принадлежат root, команда выполняется через sudo или под администратором;
  2. значение за аппаратным максимумом ядро тихо обрезает, записанное число всегда сравнивают с тем, что вернул повторный cat;
  3. путаница зон: лимит пакета охватывает весь процессор, а подзоны core и uncore лишь распределяют его внутри, отдельного лимита на каждое ядро в этой модели нет;
  4. путаница единиц: окно задается в микросекундах, мощность в микроваттах, запись 8 вместо 8000000 или 45 вместо 45000000 желаемого эффекта не даст.

Потолок мощности уместен на компактных серверах и в контейнерных площадках, где важна плотность размещения, на ноутбуках ради прохладной и тихой работы, в домашней машине, когда хочется снизить шум и нагрев. Платой служит предсказуемое снижение пиковой производительности: компиляция и архивация станут дольше, тогда как отзывчивость рабочего стола и веб-серверы с редкими запросами почти ничего не заметят. Отдельный случай это тип управления idle-injection: вместо лимита ватт он принудительно вставляет в работу ядер паузы и применяется как аварийное охлаждение при перегреве. В связке sysfs, счетчик энергии и лимиты дают администратору полный цикл: посмотреть, ограничить, измерить и закрепить результат, причем каждая операция занимает секунды и не требует перезагрузки.