Привет, Хабр! Уверен, многие сталкивались с тормозами сервера, долгой загрузкой страниц. Логи молчат, нужно искать виновника. Системный мониторинг демонстрирует, что CPU вроде не загружен, память не полностью израсходована, а отклик системы оставляет желать лучшего.
В такие моменты стандартных утилит вроде top или htop часто недостаточно, нужен более детальный анализ. С этим мне приходится периодически сталкиваться, из-за чего и были написаны 3 bash-скрипта. Они дают сбор ключевых метрик системы для дальнейшего разбора.
Поиск аномалий памяти
Первый скрипт выходит за рамки простого показа free -m. Его задача - найти процессы, которые ведут себя подозрительно: едят память слишком быстро, удерживают её без необходимости или создают чрезмерную нагрузку на подсистему ввода-вывода.
memory_analysis.sh:
#!/bin/bash
echo "=== Анализ памяти и процессов-кандидатов на OOM Killer ==="
echo "Временная метка: $(date)"
echo ""
# 1. Общая картина по памяти
echo "1. Сводка по памяти:"
echo "-------------------"
free -h
echo ""
# 2. Детализация по оперативной памяти
echo "2. Детализация использования RAM и Swap:"
echo "----------------------------------------"
cat /proc/meminfo | grep -E "(MemTotal|MemAvailable|SwapTotal|SwapFree|SwapCached)"
echo ""
# 3. Поиск процессов, потребляющих много памяти
# Сортируем по резидентной памяти (RSS), которая реально находится в RAM
echo "3. Первые 10 процессов по использованию резидентной памяти (RSS):"
echo "-------------------------------------------------------------"
ps aux --sort=-%mem | awk 'NR<=11 {printf "%-8s %-6s %-4s %-8s %-8s %s\n", $2, $1, $4, $3, $6/1024" MB", $11}'
echo ""
# 4. Анализ памяти, которая ждет записи на диск
# Это может быть индикатором нагрузки на I/O
echo "4. Процессы с большим объемом ожидающей записи памяти:"
echo "------------------------------------------------------------------"
for pid in $(ps -eo pid --no-headers); do
if [ -f /proc/$pid/statm ]; then
dirty_pages=$(grep -i "Private_Dirty:" /proc/$pid/smaps 2>/dev/null | awk '{sum += $2} END {print sum}')
if [ -n "$dirty_pages" ] && [ "$dirty_pages" -gt 1000 ]; then
proc_name=$(cat /proc/$pid/comm 2>/dev/null)
dirty_kb=$((dirty_pages * 4)) # переводим страницы в килобайты (обычно 4KB на страницу)
echo "PID: $pid, Имя: $proc_name, Грязная память: $dirty_kb KB"
fi
fi
done | sort -k6 -nr | head -10
echo ""
# 5. Мониторинг "давления" памяти (PSI - Pressure Stall Information)
# Показывает, сколько времени процессы проводят в ожидании памяти
echo "5. Давление на память (PSI):"
echo "---------------------------"
if [ -f /proc/pressure/memory ]; then
cat /proc/pressure/memory
else
echo "Информация о ''давлении'' памяти не поддерживается в этой версии ядра."
fi
echo ""
Как это работает и на что смотреть:
Пункты 1 и 2 дают нам общую отправную точку. Важно смотреть не на
used, а наavailableилиfree + buffers/cache. Еслиavailableпамять иссякает, система начинает активно использовать swap.Пункт 3 - в целом, классика. Но здесь сортировка идет именно по резидентной памяти. Столбец
RSSпоказывает, сколько физической RAM реально занимает процесс. Резкий рост RSS у одного из процессов - явный сигнал.Пункт 4 - это уже глубокая диагностика. Большое скопление такой памяти у одного процесса может указывать на то, что он генерирует много данных и не успевает их сбрасывать, создавая нагрузку на диск подсистему. Если этот показатель постоянно высокий, стоит проверить настройки сброса «грязных» страниц (
/proc/sys/vm/dirty_ratioиdirty_background_ratio).Пункт 5 (Pressure Stall Information) - современная и очень точная метрика. Он показывает, в какой степени процессы были вынуждены простаивать из-за нехватки памяти. Если значение
fullрастет, это прямой признак того, что памяти физически не хватает и это уже бьет по производительности.
Пример вывода:

Pressure Stall Information (PSI)
Pressure Stall Information (PSI) - индикатор перегрузки системы.
Когда PSI регистрирует рост показателей - значит, что процессы начали скапливаться в очередях. Например, если данные о памяти показывают увеличение значений «full» - прямой сигнал о том, что системе не хватает оперативной памяти, и процессы вынуждены долго ждать, пока освободится место.
В отличие от классических метрик (таких как загрузка CPU или свободная память), PSI измеряет не объём ресурсов, а время ожидания. Это позволяет заранее заметить проблемы - ещё до того, как сервер начнет «лагать» или приложения перестанут отвечать.
Глубокий анализ дискового ввода-вывода
Второй скрипт призван найти не просто большие файлы, а именно те процессы, которые создают основную нагрузку на дисковую подсистему прямо сейчас.
io_analyzer.sh:
#!/bin/bash
echo "=== Анализ дискового ввода-вывода и поиск файловых дескрипторов ==="
echo "Временная метка: $(date)"
echo ""
# 1. Общая статистика ввода-вывода с помощью iostat
echo "1. Общая статистика I/O (первые 3 секунды - усреднение, потом live):"
echo "------------------------------------------------------------------"
if command -v iostat &> /dev/null; then
iostat -dx 1 3
else
echo "Утилита 'iostat' не установлена. Установите пакет 'sysstat'."
fi
echo ""
# 2. Использование места на диске точками монтирования
echo "2. Использование дискового пространства:"
echo "---------------------------------------"
df -h | sort -k5 -hr
echo ""
# 3. Поиск процессов, ведущих активную дисковую деятельность
# Используем pidstat, если доступен
echo "3. Процессы с активной дисковой нагрузкой (KB/sec):"
echo "--------------------------------------------------"
if command -v pidstat &> /dev/null; then
pidstat -dl 1 1 | sort -k6 -nr | head -15
else
echo "Утилита 'pidstat' не установлена. Установите пакет 'sysstat'."
echo "Альтернатива: использование /proc//io (более сложный парсинг)."
fi
echo ""
# 4. Поиск процессов, удерживающих открытыми много файловых дескрипторов
# Это может указывать на утечку дескрипторов или на процесс, работающий с огромным количеством файлов
echo "4. Первые 10 процессов по количеству открытых файловых дескрипторов:"
echo "---------------------------------------------------------------"
for pid in $(ps -eo pid --no-headers); do
if [ -d /proc/$pid/fd ]; then
fd_count=$(ls -1 /proc/$pid/fd 2>/dev/null | wc -l)
proc_name=$(ps -p $pid -o comm= 2>/dev/null)
if [ -n "$proc_name" ]; then
echo "PID: $pid, Имя: $proc_name, FD: $fd_count"
fi
fi
done | sort -t',' -k3 -nr | head -10
echo ""
# 5. Поиск больших файлов в открытых дескрипторах
# Может помочь найти процесс, который ведет запись в огромный лог или временный файл
echo "5. Процессы с открытыми большими файлами (>100MB):"
echo "-------------------------------------------------"
for pid in $(ps -eo pid --no-headers); do
if [ -d /proc/$pid/fd ]; then
for fd in /proc/$pid/fd/*; do
file_size=$(stat -Lc%s "$fd" 2>/dev/null)
if [ -n "$file_size" ] && [ "$file_size" -gt 104857600 ]; then # 100MB в байтах
file_name=$(readlink -f "$fd" 2>/dev/null)
proc_name=$(ps -p $pid -o comm= 2>/dev/null)
file_size_mb=$((file_size / 1024 / 1024))
echo "PID: $pid, Процесс: $proc_name, Файл: $file_name, Размер: ~$file_size_mb MB"
fi
done
fi
done
Как это работает и на что смотреть:
Пункт 1 (
iostat). Ключевые колонки:%util(процент загрузки устройства, близкий к 100% означает, что диск - узкое место),await(среднее время ожидания I/O-операции, в мс). Высокийawaitпри высоком%util- явный признак перегруженности диска.Пункт 3 (
pidstat -dl) - показывает, сколько килобайт в секунду каждый процесс читает и пишет на диск. Это позволяет сразу идентифицировать самого активного виновника.Пункт 4 - наверное больше про "тихие" проблемы. Процесс, который открыл тысячи файловых дескрипторов и не закрывает их, может со временем исчерпать лимиты системы (
ulimit -n), что приведет к ошибкам у этого и других процессов.Пункт 5. Иногда процесс (часто это СУБД или кэширующий демон) может открыть огромный файл для чтения/записи. Этот цикл может найти такой процесс и показать, с каким именно файлом он работает. Нередко это указывает на неверно настроенные логи или временные файлы, разросшиеся до нескольких ГБ.
Пример вывода:


Детектор сетевых аномалий
Третий скрипт фокусируется на сетевой активности. Он помогает найти процессы, устанавливающие подозрительно много соединений, или обнаружить неожиданно высокий сетевой трафик.
network_analysis.sh:
#!/bin/bash
echo "=== Анализ сетевых соединений и трафика ==="
echo "Временная метка: $(date)"
echo ""
# 1. Статистика по сетевым интерфейсам
echo "1. Статистика по сетевым интерфейсам:"
echo "------------------------------------"
if command -v ip &> /dev/null; then
ip -s link
else
netstat -i
fi
echo ""
# 2. Сводка по установленным соединениям
echo "2. Количество соединений по состояниям:"
echo "--------------------------------------"
netstat -tun | awk '/^tcp/ {state[$6]++} END {
for (s in state) print s, state[s]
}' | sort -rn -k2
echo ""
# 3. Процессы, имеющие много сетевых соединений
echo "3. Первые 10 процессов по количеству сетевых соединений:"
echo "---------------------------------------------------"
netstat -tunp 2>/dev/null | awk '$6=="ESTABLISHED"{print $7}' | cut -d'/' -f1 | sort | uniq -c | sort -rn | head -10 | while read count pid; do
if [ -n "$pid" ] && [ "$pid" != "-" ]; then
proc_name=$(ps -p $pid -o comm= 2>/dev/null)
echo "Соединений: $count, PID: $pid, Процесс: $proc_name"
fi
done
echo ""
# 4. Поиск процессов, слушающих нестандартные порты
echo "4. Слушающие порты (исключая стандартные 22, 80, 443, 5432 и т.д.):"
echo "------------------------------------------------------------------"
netstat -tunlp | grep LISTEN | while read line; do
port=$(echo $line | awk '{print $4}' | awk -F: '{print $NF}')
pid_program=$(echo $line | awk '{print $7}')
# Исключаем некоторые стандартные порты
if [[ "$port" =~ ^(22|80|443|53|25|587|993|995|5432|3306|27017|11211|6379)$ ]]; then
continue
fi
pid=$(echo $pid_program | cut -d'/' -f1)
program=$(echo $pid_program | cut -d'/' -f2-)
echo "Порт: $port, PID: $pid, Процесс: $program"
done
echo ""
# 5. Мониторинг сетевых ошибок и отброшенных пакетов
echo "5. Статистика сетевых ошибок и отбросов:"
echo "---------------------------------------"
if command -v ip &> /dev/null; then
echo "Интерфейс | Ошибки(RX/TX) | Сбросы(RX/TX)"
echo "-------------|---------------|--------------"
ip -s link show | awk '
/^[0-9]+:/ {iface=$2; getline}
/RX.*bytes/ {getline; rx_err=$2; tx_err=$10; getline; rx_drop=$2; tx_drop=$10;
printf "%-12s | %-13s | %-12s\n", iface, rx_err"/"tx_err, rx_drop"/"tx_drop}'
else
netstat -i | awk 'NR>2 {print $1, $5"/"$9, $6"/"$10}'
fi
Как это работает и на что смотреть:
Пункт 2 показывает распределение TCP-соединений по состояниям. Большое количество соединений в состоянии
TIME_WAITилиCLOSE_WAITможет указывать на проблемы с сетевым стеком приложения или на его неумение корректно закрывать соединения.Пункт 3. Неожиданно высокое число соединений у одного процесса (например, у веб-сервера или базы данных) может быть как нормой, так и признаком DDoS-атаки, утечки соединений в приложении или активности бота.
Пункт 4 - безопасность и осведомленность. Скрипт находит службы, слушающие порты, которые не являются общеизвестными (например, 22 для SSH или 80 для HTTP). Это помогает быстро обнаружить неавторизованные или забытые сервисы.
Пункт 5 - диагностика проблем на сетевом уровне. Растущее количество ошибок (
errors) или сброшенных пакетов (drops) на интерфейсе часто указывает на проблемы с сетевым оборудованием, его перегруженность или неверную настройку.
Пример вывода:

В пункте 4 отсутствует вывод из-за отсутствия слушающих портов, а стандартные (моё подключение по SSH) исключены из вывода в скрипте.
Заключение
Эти скрипты - очень хороший стартовый набор для быстрой диагностики. Они помогают перейти от недоумения (с чего начать...) к конкретным данным: «процесс N ест всю память» или «процесс N создает огромную нагрузку на диск». С данными, полученными от скриптов, проще искать причину.
Важное уточнение. Данные скрипты не заменят Zabbix или Prometheus, создавались они не для этих целей. Они скорее являются решением, где тяжелые системы мониторинга избыточны. Я использую их на нескольких лёгких VPS, куда громоздить мониторинг не имеет смысла.
Комментарии (21)

abask
07.10.2025 15:40скормил первый скрипт дипсик
говорит, что имеется критическая ошибка в анализе dirty pages.

eternaladm Автор
07.10.2025 15:40Повторил Ваш запрос. Мне БЯМ выдала, что все корректно, странно. Я изучу документацию, вполне допускаю подобного рода ошибку.

GritsanY
07.10.2025 15:40В network_analysis.sh на всех моих машинах в пункте 2 bash выдаёт
awk: cmd. line:2: (FILENAME=- FNR=18) fatal: attempt to use scalar `state' as an array
eternaladm Автор
07.10.2025 15:40Принял к сведению, вечером погоняю больше тестов, внесу правки. VPS на Ubuntu корректно скрипт воспринимают…

AlexHighTower
07.10.2025 15:40и третий стрипт
line 54: syntax error near unexpected token `;&' line 54: `if command -v ip &> /dev/null; then'

aio350
07.10.2025 15:40В строке
netstat -tunl | awk '/^tcp|^udp/ {state=$6} END {вместоstate=$6должно бытьstate[$6]++

RedEyedAnonymous
07.10.2025 15:40Строка 54: "if command -v ip &> /dev/null; then"
За экранировалось "&>"
eternaladm Автор
07.10.2025 15:40Спасибо, исправлено! Вроде форматировал, недоглядел…

RedEyedAnonymous
07.10.2025 15:40Ещё при выводе форматирование у п.5, ломается, выводится без выравнивания столбцов.
Возможно, такие скрипты имело бы смысл на питоне (перле, ...) писать.
Т.е. обработку вывода netstat и прочего средствами языка, без awk и т.п.
И ошибки обрабатывать удобнее, и возможностей по чистке от мусора/форматированию вывода больше.
Кроме того, ip и некоторые другие тулзы умеют вывод в json или другой машиночитаемый, было б меньше ненадёжных соплей с парсингом текста.
Jalart
07.10.2025 15:40Питон/Перл не везде может быть, в отличие от стандартных linux-утилит.А потом еще несовместимости самих питонов/перлов вылезут - ещё и с этим бороться.

eternaladm Автор
07.10.2025 15:40Я солидарен полностью, есть в архиве и на Питоне скрипты со схожим назначением. Просто в некоторых ситуациях необходимо «классическое» решение, без установки дополнительных пакетов.

AlexHighTower
07.10.2025 15:40Статистика сетевых ошибок и отбросов: неправильно вырезается
--------------------------------------- Интерфейс | Ошибки(RX/TX) | Сбросы(RX/TX) | ---------|---------------|--------------| lo: bytes/ 1263100781/ eth0: bytes/ 0/ eth1: bytes/ 10595809052/ eth2: bytes/ 41060571/ eth3: bytes/ 0/
eternaladm Автор
07.10.2025 15:40Исправил полностью, должно отображаться корректно. Мои пару тестов выдали корректное отображение.

Borelli
07.10.2025 15:40Если выгрузить все три файла рядом, возникает вопрос - почему io - analyzer, а остальные два - analysis? :)

eternaladm Автор
07.10.2025 15:40Изначально скрипты не подразумевались как «комплекс», писались в разное время. Потом, уж так получилось, совместил в небольшую «сборку» скриптов. Я могу поменять, если Вас это смущает.
THEOILMAN
Довольно часто об этом говорю, и тут опять к месту: Штука для одменов, которые одменели 25 лет винду и тут нагрянуло импортозамещение)