Что такое load average linux и почему 542 - это не приговор
Давай сразу про миф. Многие думают, что load average - это "загрузка процессора в процентах". Это неправда. Load average в Linux - это среднее число процессов (точнее, потоков, в ядре их называют scheduling entities), которые в данный момент чего-то хотят: либо крутятся на CPU, либо стоят в очереди за CPU, либо ждут диск в особом состоянии.
Формально в счётчик попадают два типа задач:
- Runnable (R, в ядре TASK_RUNNING) - готовы выполняться: либо уже на ядре, либо в очереди ждут своей доли CPU.
- Uninterruptible sleep (D, в ядре TASK_UNINTERRUPTIBLE) - "непрерываемый сон". Задача застряла в ожидании ресурса, обычно дискового ввода-вывода (или сетевой ФС вроде NFS), а иногда это ожидание ядерной блокировки (мьютекса, семафора). Её даже сигналом не разбудишь, пока ресурс не ответит. Сигнал kill -9 такую задачу не снимет - она невидима для сигналов до возврата из ядра.
Запомни главную аналогию. Load average - это не спидометр (как быстро едем), а число людей в очереди к кассе плюс те, кто уже у кассы. Очередь из 542 человек к одной кассе - катастрофа. Та же очередь к 256 кассам - норма.
Отсюда железное правило чтения load average linux: всегда дели на число ядер. Узнаём число ядер:
Код: Выделить всё
nproc
# или подробнее (логические CPU, ядра, сокеты):
lscpu | grep -E '^CPU\(s\):|Core|Socket|Thread'
Главная тайна load average: почему в Linux он "лукавый" (разбор статьи Брендана Грегга)
Есть классическая статья Брендана Грегга "Linux Load Averages: Solving the Mystery" (2017, перевод на Хабре от VK). Она разбирает ровно то заблуждение, с которого мы начали, и доводит его до самого корня - до строчек ядра и до письма 1993 года. Перескажу суть, потому что без неё load average так и останется магическим числом.
История: патч 1993 года. В ранних Unix и в самом раннем Linux load average был честным "спросом на CPU" - считал только задачи в TASK_RUNNING. Но 29 октября 1993 года инженер Matthias Urlichs прислал крошечный патч (вошёл в Linux 0.99.14, ноябрь 1993; в 0.99.13 его ещё не было), который добавил в учёт задачи в состоянии TASK_UNINTERRUPTIBLE. Его обоснование (в переводе с оригинала письма) звучало так:
Логика простая и сильная: если поставить диск медленнее, система станет хуже, а старый load average при этом уменьшался (процессы дольше спят в ожидании io, а спящих он не считал). Это абсурд. Urlichs хотел, чтобы число отражало "спрос на систему в целом" с точки зрения человека, а не только очередь к процессору. Спустя 24 года, когда Грегг его об этом спросил, он сформулировал то же самое ещё яснее: смысл load average - дать число, показывающее, насколько система занята с человеческой точки зрения; машина, заваленная дисковым io, может быть жутко тормозной, но иметь TASK_RUNNING-нагрузку всего 0.1 - и такое число никому не поможет.Проблема в том, что процессы, которые свопятся или ждут "быстрый", то есть непрерываемый, ввод-вывод, тоже потребляют ресурсы. Выглядит контринтуитивно, что load average падает, когда ты меняешь быстрый диск под своп на медленный.
Вот почему в Linux load average - это не "спрос на CPU", а "спрос на систему" (system load): runnable плюс uninterruptible. Это сознательное проектное решение 1993 года, а не баг. И отсюда же растёт его двусмысленность: по одному LA ты не отличишь CPU-затык от io-затыка. Высокий load в Linux честно означает "системе тяжело", но не говорит, почему тяжело.
Из чего реально складывается число. Грегг не поверил на слово и измерил вклады в load с помощью off-CPU flame graphs (инструмент offcputime из bcc/eBPF с фильтром по состоянию TASK_UNINTERRUPTIBLE). На 8-ядерной машине во время распаковки tar он разложил load average 1.19 буквально по слагаемым:
- 0.33 - время tar на CPU (это и есть "честный" CPU-спрос);
- 0.67 - tar в непрерываемом сне на чтении с диска (тот самый D-state);
- 0.04 - прочие потребители CPU в ядре;
- 0.11 - ядерные воркеры в непрерываемом io.
Три числа 1/5/15 - это не "среднее за минуту", а экспоненциальное затухание
Второй большой инсайт из той же статьи: три числа в loadavg - это не простое среднее арифметическое за 1, 5 и 15 минут. Это экспоненциально затухающие скользящие средние (exponentially-damped moving averages). Ядро раз в 5 секунд берёт текущее число активных задач и подмешивает его в накопленное значение по формуле затухания:
Код: Выделить всё
новое = старое * EXP + текущее * (1 - EXP)Код: Выделить всё
#define EXP_1 1884 /* 1/exp(5sec/1min) */
#define EXP_5 2014 /* 1/exp(5sec/5min) */
#define EXP_15 2037 /* 1/exp(5sec/15min) */Практическое следствие, которое многих удивляет: если на простаивающей системе запустить ровно один CPU-bound поток и подождать ровно 60 секунд, одноминутный load average покажет не 1.0, а примерно 0.62. Он ещё не успел "догнать" реальную нагрузку - экспонента подходит к цели асимптотически. Поэтому одноминутное число реагирует быстро, но дёргано; пятнадцатиминутное - медленно и плавно. Это не среднее за прошедшую минуту, а "взвешенная память" системы с разной длиной.
Зато это даёт бесплатный тренд без всякого мониторинга. Как читать три числа вместе:
- 1-минутное сильно больше 15-минутного (например 12.0 / 3.0 / 1.5) - нагрузка растёт прямо сейчас, проблема свежая, реагируй немедленно.
- 1-минутное меньше (1.5 / 4.0 / 9.0) - пик уже прошёл, система выдыхает, возможно ты опоздал на разбор и ловишь хвост.
- Все три близки - стабильный установившийся режим, нагрузка ровная.
- LA - это грубый индикатор тренда: "стало ли системе хуже, чем было час назад". Для этого он отличный и дешёвый (одно чтение файла).
- Дели на число ядер, чтобы прикинуть масштаб, но помни про примесь D-state: "load на ядро = 2" может быть и реальной CPU-перегрузкой вдвое, и сотней процессов, висящих на отвалившемся NFS при пустом процессоре.
- Абсолютный порог сам по себе бессмысленен: load 25 на 2 ядрах и на 64 ядрах - совершенно разные ситуации, а ещё надо знать, из чего этот load набрался.
- Для точной диагностики LA принципиально недостаточно - он смешивает CPU, диск и блокировки в одно число и по определению не может их разделить. Дальше нужны прицельные метрики: mpstat -P ALL 1 (загрузка по ядрам), pidstat 1 (по процессам), vmstat 1 (колонка r - длина runqueue), iostat -x 1 (диски) и - главное на 2026 - PSI, к которому мы и идём.
proc linux: где система прячет настоящие linux метрики
Теперь к самому важному вопросу урока: откуда берутся цифры? Ответ - из псевдофайловой системы /proc. Это не файлы на диске. Это окно прямо в ядро: каждый "файл" в /proc ядро рисует на лету в момент, когда ты его читаешь. Открыл - получил свежий снимок состояния. Поэтому у многих из них размер 0, а mtime бессмысленна.
Ключевая мысль урока: почти все привычные утилиты (top, uptime, free, ps, vmstat) - это просто красивые обёртки над /proc. Они читают те же текстовые файлы, что доступны и тебе. Значит, если top не установлен (а на голом контейнере или урезанном образе так и бывает), метрику всё равно можно достать голыми руками.
Сам load average лежит здесь:
Код: Выделить всё
cat /proc/loadavg
0.52 0.43 0.38 2/431 18922- 0.52 0.43 0.38 - те самые средние за 1, 5 и 15 минут (число задач в очереди R плюс задачи в D), посчитанные экспоненциальным затуханием, как разобрали выше.
- 2/431 - до слэша число runnable прямо сейчас (готовы бежать), после слэша - сколько всего scheduling entities существует в системе.
- 18922 - PID, который был создан последним. По нему, кстати, видно, как бурно система плодит процессы.
Код: Выделить всё
cat /proc/stat
cpu 259812 1043 88234 9881233 12044 0 3120 0 0 0
cpu0 64903 210 22011 2470308 3011 0 812 0 0 0
...
ctxt 884512033
procs_running 2
procs_blocked 1- idle (4-я цифра) - простой. Большая - хорошо, CPU свободен.
- iowait (5-я) - сколько ядро как бы "ждало диск". Растёт - подозревай диск. Но осторожно: iowait в Linux лукавит, на многоядерных он занижен и приблизителен, опираться только на него нельзя. На 2026 правильнее смотреть io-давление через PSI, см. ниже.
- steal (8-я) - на виртуалке это время, которое гипервизор отобрал в пользу соседей. Большой steal на VPS - тебя "обкрадывает" сосед по гипервизору или ты упёрся в лимит тарифа.
Память живёт в /proc/meminfo:
Код: Выделить всё
grep -E 'MemTotal|MemFree|MemAvailable|Dirty|Writeback' /proc/meminfo
MemTotal: 16307840 kB
MemFree: 1204736 kB
MemAvailable: 9214208 kB
Dirty: 1280 kB
Writeback: 0 kBPSI: современный честный источник правды про насыщение (актуально на 2026)
Load average отвечает на вопрос "сколько задач ждёт", но не на вопрос "насколько мне от этого больно и из-за чего". Мы только что видели, в чём его слабость: историческая, лукавая метрика, которая сваливает CPU, диск и блокировки в одно затухающее число. PSI - его прямая противоположность: современная, честная метрика насыщения. Это и есть центральная пара урока - LA против PSI.
PSI - pressure stall information появился в ядре 4.20 (конец 2018). Идея в одном предложении: вместо того чтобы считать, сколько задач в очереди, ядро напрямую измеряет время, которое задачи реально простояли в ожидании ресурса. Не "542 человека в очереди", а "за последнюю минуту 15% времени кто-то стоял и ничего не делал, потому что ждал диск". Это прямое измерение потерь, а не косвенный прокси. На 2026 PSI включён по умолчанию практически везде: современные ядра, systemd, cgroup v2 (он теперь дефолтная иерархия в RHEL 9+/Astra/RED OS) - всё опирается на PSI. Это первое, куда я смотрю вместо гадания по iowait.
Три файла:
Код: Выделить всё
cat /proc/pressure/cpu
some avg10=0.00 avg60=2.31 avg300=1.07 total=98231456
cat /proc/pressure/memory
some avg10=0.00 avg60=0.00 avg300=0.05 total=412233
full avg10=0.00 avg60=0.00 avg300=0.02 total=205110
cat /proc/pressure/io
some avg10=15.40 avg60=9.22 avg300=4.10 total=512904881
full avg10=12.01 avg60=7.88 avg300=3.55 total=498120033some против full - физический смысл. Это ключ ко всему PSI:
- some - доля времени, когда хотя бы одна задача стояла в ожидании ресурса (а другие, может, работали). Это индикатор "ресурс под давлением, кому-то уже мешает".
- full - доля времени, когда все незаблокированные иначе задачи стояли одновременно, то есть никто не мог работать, потому что всё уперлось в ресурс. Это чистая, уже состоявшаяся потеря производительности и пропускной способности (для памяти - классический симптом thrashing, когда система только и делает, что свопит/перечитывает страницы).
Чем PSI честнее load average. Сравни напрямую. LA говорит "в среднем 16 задач чего-то хотят" - но не говорит, чего и насколько это вредит. PSI говорит "io full avg60 = 12" - это буквально "12% последней минуты вся полезная работа стояла колом из-за диска". Первое - косвенная оценка спроса, замешанная экспонентой; второе - прямое измерение насыщения и потерянного времени, отдельно по каждому ресурсу. Поэтому PSI разом снимает обе болезни load average: он разделяет CPU/память/io (LA их смешивает) и измеряет боль, а не очередь. Грубое правило: io some/full устойчиво выше 10-20% - диск ваше бутылочное горло, и это намного надёжнее, чем iowait из /proc/stat.
PSI по cgroup v2 - давление на конкретный сервис/контейнер. Системный /proc/pressure говорит про машину в целом. Но в мире контейнеров и systemd-юнитов важнее, кто именно давит. В cgroup v2 те же три файла лежат внутри каждой группы в cgroupfs:
Код: Выделить всё
# давление по памяти на конкретный systemd-сервис:
cat /sys/fs/cgroup/system.slice/nginx.service/memory.pressure
some avg10=0.00 avg60=1.20 avg300=0.40 total=88120
full avg10=0.00 avg60=0.95 avg300=0.31 total=61003
# io-давление внутри контейнера (docker/containerd кладут группы сюда):
cat /sys/fs/cgroup/system.slice/docker-<id>.scope/io.pressure
# а вот так быстро найти, какой сервис сейчас сильнее всех давит по io:
grep -r '' /sys/fs/cgroup/*/*/io.pressure 2>/dev/null | grep fullPSI для алертов и для systemd-oomd. Два практических применения:
- Алерты. У PSI есть механизм триггеров: процесс из userspace открывает файл pressure, пишет в него порог в формате "some|full <простой_в_мкс> <окно_в_мкс>" и засыпает на poll()/epoll(); ядро будит его ровно тогда, когда давление пробивает порог. Это честный event-driven алерт ("за 1 секунду окна 200 мс простояли на памяти") вместо опроса load average по таймеру. На графиках/в Prometheus берут avg60 или дельты total.
- systemd-oomd. Это пользовательский OOM-демон, который как раз живёт на PSI. Он следит за memory.pressure по cgroup и, если давление по памяти держится выше порога (ManagedOOMMemoryPressureLimit, скажем 60%) дольше заданного времени (DefaultMemoryPressureDurationSec, обычно порядка 20-30 секунд), заранее убивает самый прожорливый cgroup - не дожидаясь, пока в дело вступит ядерный OOM-killer по факту полного исчерпания памяти. Реакция на боль (давление), а не на катастрофу (память кончилась) - в этом вся разница и весь смысл PSI.
Заглядываем внутрь процесса: /proc/PID и устройства в /sys
Каждый процесс - это каталог /proc/<PID>/. Возьмём PID 1410:
Код: Выделить всё
cat /proc/1410/status | grep -E 'State|VmRSS|Threads'
State: D (disk sleep)
VmRSS: 204812 kB
Threads: 8- /proc/PID/fd/ - открытые файловые дескрипторы (ls -l покажет, на какие файлы и сокеты они смотрят; так ловят "кто держит удалённый файл" - удалённый, но открытый файл виден как (deleted), и место не освобождается).
- /proc/PID/smaps / smaps_rollup - детальная карта памяти по сегментам, для разбора утечек и подсчёта PSS (честная доля общей памяти).
- /proc/PID/stack - где в ядре застрял зависший процесс (нужен root). Бесценно, чтобы понять, в каком именно вызове висит задача в состоянии D - ровно тем же приёмом (стеки задач в D) Грегг разбирал, из чего набирается load average.
Код: Выделить всё
sudo cat /proc/1410/stackКод: Выделить всё
# планировщик ввода-вывода для диска sda:
cat /sys/block/sda/queue/scheduler
[mq-deadline] kyber bfq none
# вращающийся диск или SSD (1 = HDD, 0 = SSD/NVMe):
cat /sys/block/sda/queue/rotationalТипичные грабли и заблуждения
- "Load average - это проценты CPU." Нет. Это число задач в очереди плюс застрявшие в D, причём посчитанное экспоненциальным затуханием. Может расти при простаивающем процессоре.
- "Это среднее за последнюю минуту." Нет, это экспоненциально затухающее среднее с константой 1 минута. После 60 секунд ровно одной нагрузки одноминутное число будет около 0.62, а не 1.0.
- "Load 1.0 - это плохо." Само по себе - бессмысленно. 1.0 на одном ядре = впритык, на 64 ядрах = почти спит. Всегда дели на nproc (помня про примесь D-state).
- "Мало MemFree - надо срочно добавлять память." Смотри MemAvailable. Linux намеренно набивает свободную RAM кэшем, это правильно, а не утечка.
- "Высокий load - виноват CPU." Проверь procs_blocked, State: D в /proc/PID/status и /proc/pressure/io. Часто виноват диск, блокировка или зависшая сетевая шара, а не процессор.
- "iowait высокий - точно диск умирает." iowait приблизителен и на многоядерных занижен. На 2026 доверяй PSI (io some/full), а не голому iowait.
- "PSI some и full - про одно и то же." Нет: some - кому-то уже мешает; full - не может работать никто (для памяти/io это и есть настоящая боль).
- "/proc - это обычные файлы." Это интерфейс к ядру. Размер у многих 0, mtime бессмысленна, содержимое генерится в момент чтения.
Десять минут практики, всё без установки чего-либо:
- Узнай число ядер: nproc. Запиши.
- Прочитай cat /proc/loadavg. Раздели первое число на nproc - какая доля нагрузки?
- Создай CPU-нагрузку: yes > /dev/null & (запусти столько раз, сколько у тебя ядер), подожди минуту, снова глянь loadavg - оно подползло к nproc, но за 60 секунд не дотянет ровно (экспонента!). Заодно глянь cat /proc/pressure/cpu - some должно вырасти. Потом убей: kill %1 %2 %3 (или jobs, потом kill по номерам).
- Найди процессы в непрерываемом сне:
Код: Выделить всё
ps -eo pid,stat,comm | awk '$2 ~ /D/' - Посмотри живые счётчики планировщика: grep procs /proc/stat - сравни procs_running и procs_blocked.
- Сравни источники io-давления: запусти dd if=/dev/zero of=/tmp/t bs=1M count=4000 oflag=direct и параллельно смотри cat /proc/pressure/io - как растёт io some, а на full проявляется реальный затык. Сравни с iowait из /proc/stat - почувствуй, насколько PSI нагляднее.
- Для бонуса: найди cgroup своего шелла и глянь его давление - cat /proc/self/cgroup, затем cat /sys/fs/cgroup/<путь>/io.pressure.
- Из каких двух состояний процессов складывается load average в Linux и какое из них добавили патчем 1993 года?
- Почему после 60 секунд ровно одной CPU-нагрузки одноминутный load average показывает ~0.62, а не 1.0?
- load average показывает 16.0. Это много или нормально? Какой одной командой ты это определишь и почему ответ всё равно будет неполным?
- Какой файл - первоисточник для top, uptime и free, и почему его можно читать напрямую?
- У процесса State: D в /proc/PID/status. На что это намекает и куда смотреть дальше (хотя бы два места)?
- Чем отличаются some и full в /proc/pressure/io и почему PSI честнее показывает узкое место, чем iowait и чем load average?
Load average - это очередь задач (runnable + uninterruptible D), а не проценты CPU; это сознательное решение Matthias Urlichs от 1993 года ("спрос на систему", а не только на CPU), и считается оно экспоненциальным затуханием с окнами 1/5/15 минут, а не простым средним. Читать его надо всегда делёным на число ядер и понимая, что по одному LA не отличить CPU-затык от io-затыка. Все привычные утилиты - обёртки над /proc, и любую метрику можно достать голыми руками: /proc/loadavg, /proc/stat, /proc/meminfo, /proc/PID/. Железо и настройки ядра - в /sys. А чтобы не гадать "это CPU, память или диск и кто виноват" - на 2026 есть PSI в /proc/pressure/ и в cgroup v2: он прямо измеряет процент потерянного на ожидании времени (some/full, avg10/60/300, total) по каждому ресурсу и по каждому сервису, питает алерты и systemd-oomd. Связка простая: LA - заметить, что стало хуже; PSI - понять, чего именно не хватает и кому. Когда привычной тулзы нет, ты теперь не беспомощен: знаешь, где у системы лежит правда. (На Astra Linux и RED OS всё это работает один в один - ядро то же самое, cgroup v2 и PSI на месте.)