strace в бою: почему программа висит, падает или тормозит

Рейтинг: 45.3% · 9 голосов
Подробный курс по диагностике и производительности Linux для админов и DevOps: с чего начать, методология USE, логи (journalctl, dmesg), процессы, CPU, память и OOM, диск и IO (iostat), сеть (ss, tcpdump), системные вызовы и strace, ltrace, perf, флеймграфы, ftrace, eBPF и bpftrace, lsof, мониторинг. Разбор каждого инструмента с чтением вывода.
Ответить
Аватара пользователя
Dmitry_SRE
Сообщения: 47
Зарегистрирован: 11 май 2026, 05:31

strace в бою: почему программа висит, падает или тормозит

Сообщение Dmitry_SRE »

Оглавление курса (47)
  1. С чего начать диагностику Linux: методология вместо паники
  2. Первые 60 секунд: экспресс-диагностика нагруженного сервера
  3. Методологии диагностики: USE, RED и здравый смысл
  4. Логи systemd через journalctl: где искать причину
  5. Где лежат логи Linux: /var/log, dmesg и rsyslog
  6. Источники правды: load average, /proc и /sys
  7. Процессы Linux: ps, pstree и состояния процессов
  8. Интерактивный мониторинг: top и htop
  9. Метрики процессов во времени: pidstat
  10. Приоритеты и ограничения: nice, ionice, cgroups
  11. Сигналы и зависшие процессы: kill, и что делать с D-state
  12. Загрузка CPU: user, system, iowait, steal и контекст-свитчи
  13. Диагностика CPU по ядрам: vmstat и mpstat
  14. Профилирование CPU: perf top и поиск пожирателя
  15. Частоты, троттлинг и NUMA: turbostat и numastat
  16. Память Linux: RSS, VSZ, page cache и миф о нехватке памяти
  17. Сколько памяти занято: free, /proc/meminfo и vmstat
  18. Поиск утечек и пожирателей памяти: smem, pmap, smaps
  19. Swap и подкачка: swapon, swappiness, когда своп - это боль
  20. OOM killer: кто и за что убил процесс
  21. Память ядра и slab: slabtop и куда уходит RAM
  22. Подсистема ввода-вывода: путь запроса от приложения до диска
  23. Диагностика диска: iostat и чтение await, %util
  24. Кто грузит диск: iotop и атрибуция io процессам
  25. Файловые системы: df, du, иноды и куда делось место
  26. Глубокая диагностика блочного слоя: blktrace и biolatency
  27. Кеш страниц и грязные данные: dirty pages, fsync, drop_caches
  28. Сетевой стек Linux: путь пакета и где возникают задержки
  29. Сокеты и соединения: ss и netstat
  30. Захват трафика: tcpdump для диагностики сети
  31. Задержки и потери в сети: ping, mtr, диагностика латентности
  32. Сеть как источник проблем: nftables, conntrack, дропы
  33. Что такое системный вызов и зачем его трассировать
  34. strace: трассировка системных вызовов на практике
  35. strace в бою: почему программа висит, падает или тормозит (вы здесь)
  36. ltrace: трассировка вызовов библиотек
  37. Накладные расходы трассировки и безопасные альтернативы
  38. perf: универсальный профайлер Linux
  39. Флеймграфы: визуализация профиля производительности
  40. Трассировка ядра: ftrace и trace-cmd
  41. eBPF: революция в наблюдаемости Linux
  42. Инструменты eBPF на практике: BCC и bpftrace
  43. lsof: открытые файлы, дескрипторы, кто держит файл и порт
  44. Разбор кейса: приложение тормозит - пошаговая диагностика
  45. Непрерывный мониторинг: sar, node_exporter, Grafana, Zabbix
  46. Диагностика в контейнерах: cgroups, лимиты, Docker и Kubernetes
  47. Карта инструментов и сквозной разбор инцидента производительности
Бывает так: запускаешь сервис, а он висит. Или падает на старте без внятной ошибки. Или работает, но почему-то тормозит на ровном месте. Логи молчат или врут, в коде глазами ничего не видно, а заказчик уже пишет в чат. Вот ровно в этот момент достаёшь strace - и он показывает то, что программа реально делает на уровне ядра, а не то, что про себя думают её логи.

В этом уроке разберём, почему программа висит на Linux и как это увидеть, как искать пропавший конфиг, как ловить медленные места через strace тайминги и сводку strace c. Всё на конкретных кейсах, с разбором вывода по полям. И отдельно поговорим, где в 2026 году strace уже стоит заменить на eBPF - чтобы не положить прод трассировкой.

Что вообще делает strace и как читать его строки

Программа сама почти ничего не умеет: открыть файл, прочитать байты из сокета, выделить память, дождаться события - всё это она просит у ядра через системные вызовы (syscalls). strace садится между программой и ядром через механизм ptrace и записывает каждый такой запрос: что попросили, с какими аргументами и что ядро ответило.

Аналогия простая. Представь, что программа - это посетитель за стеклом в банке, а ядро - операционист. Сам посетитель тебе ничего не расскажет, но ты слышишь каждую его реплику в окошко: "дайте файл /etc/app.conf", "прочитайте мне из сокета номер 7", "подождите, я жду блокировку". strace - это и есть стенограмма всех реплик в окошко.

Одна строка вывода читается так:

Код: Выделить всё

openat(AT_FDCWD, "/etc/app/config.yml", O_RDONLY) = -1 ENOENT (No such file or directory)
Разбираем по частям:
  • openat - имя системного вызова (тут: открыть файл). Современный glibc для открытия почти всегда зовёт именно openat, а не старый open - не ищи в выводе open, фильтруй по openat.
  • в скобках - аргументы: AT_FDCWD значит "относительно текущего каталога", дальше путь и флаги (O_RDONLY - только чтение).
  • = - и дальше результат. Если число >= 0 - это, как правило, файловый дескриптор или количество байт, всё хорошо. Если -1 - вызов завершился ошибкой.
  • ENOENT - символьное имя ошибки (errno), а в скобках её расшифровка. Это и есть главное: вот по какому пути файл не нашёлся.
Запомни четыре ошибки, которые встречаются чаще всего: ENOENT - нет такого файла или каталога, EACCES - доступ запрещён (права/владелец/SELinux), ECONNREFUSED - на том конце никто не слушает порт, ETIMEDOUT - соединение не уложилось в таймаут. Уже по ним половина проблем диагностируется без чтения исходников.

Ставится strace из штатного репозитория: sudo apt install strace (Debian/Ubuntu/Astra Linux), sudo dnf install strace (Fedora/RHEL/RED OS). На 2026 актуальна ветка strace 6.x; если версия совсем старая (5.x и ниже) - часть удобных флагов вроде -yy может вести себя беднее, обнови пакет. Чтобы прицепиться к чужому процессу, обычно нужен root, поэтому почти все боевые команды идут через sudo.

Изображение

Кейс 1. Программа висит: смотрим, на каком syscall застряла

Это классика жанра и самый частый вопрос - почему программа висит linux и где она зависла. Процесс жив, в ps висит, CPU не ест, но ничего не делает. Прицепляемся к нему по PID:

Код: Выделить всё

sudo strace -p 12345
Если процесс реально завис, ты увидишь одну незавершённую строку, и strace замрёт на ней. Например:

Код: Выделить всё

strace: Process 12345 attached
futex(0x7f9c0a280a0c, FUTEX_WAIT_PRIVATE, 2, NULL
Обрати внимание: закрывающей скобки и = нет. Это значит, что вызов начался, но ещё не вернулся - программа прямо сейчас сидит внутри него и ждёт. На каком именно syscall застряла - это и есть диагноз. Расшифровка самых частых "висяков":
  • futex(..., FUTEX_WAIT...) - ждёт блокировку (мьютекс) от другого потока. Частый признак дедлока или того, что другой поток сам застрял и не отпускает лок. Один futex в покое - норма; тревога, когда он не сдвигается минутами.
  • read(7, ...) или recvfrom(...) - ждёт данные, которые не приходят. Цифра 7 - это файловый дескриптор; данные ждут из файла, пайпа или сети. Чаще всего - повисший ответ от базы или внешнего API.
  • poll(...), epoll_wait(...), select(...) - ждёт события сразу на нескольких дескрипторах. Для сетевого демона (nginx, postgres) это нормальное состояние покоя: он просто ждёт нового запроса. Тревога - только если запросы идут, а он на них не реагирует.
  • connect(...) - ждёт, пока установится соединение. Если висит надолго - сетевой таймаут: хост недоступен, файрвол режет молча, не тот порт.
  • nanosleep(...) / clock_nanosleep(...) - программа сама себя усыпила. Это не баг, а явный sleep в коде; смотри, не зациклилась ли логика повторов.
Дальше по дескриптору можно понять, чего именно ждём. И тут на 2026 есть удобный приём вместо ручного хождения в /proc: добавь флаг -yy, и strace сам подпишет дескрипторы прямо в строке.

Код: Выделить всё

sudo strace -yy -p 12345
recvfrom(7<TCP:[10.0.0.5:54122->10.0.0.9:5432]>, ...
Видим, что fd 7 - это TCP-соединение на порт 5432 (это postgres), и сразу понятно: висим на ответе из базы. Если -yy недоступен, работает старый способ через /proc:

Код: Выделить всё

sudo ls -l /proc/12345/fd/7
lrwx------ 1 app app 64 jun 15 12:30 /proc/12345/fd/7 -> socket:[849213]
Слово socket значит, что висим на сети, и дальше идём в ss -tp смотреть это соединение по inode 849213. Если бы там был путь к файлу - значит, ждали с диска. Вот так за одну-две команды "программа просто висит" превращается в "висит на чтении из сокета к базе на 5432".

Кейс 2. Не находит файл или конфиг

Программа жалуется "config not found" или молча падает на старте, а ты уверен, что конфиг лежит на месте. Проблема в том, что она ищет его не там, где ты думаешь. Отфильтруем только открытия файлов через -e trace:

Код: Выделить всё

sudo strace -f -e trace=openat -p 12345
Флаг -f (follow) важен: он тащит трассировку за дочерними потоками и процессами, иначе пропустишь половину. В выводе ищем строки с -1 ENOENT:

Код: Выделить всё

openat(AT_FDCWD, "/etc/myapp/config.yml", O_RDONLY) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/usr/local/etc/myapp/config.yml", O_RDONLY) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "./config.yml", O_RDONLY) = 3
Картина как на ладони: программа последовательно перебирает пути и в двух системных каталогах файла нет (ENOENT), а нашла она ./config.yml рядом с собой и получила дескриптор 3. Если ты правил конфиг в /etc/myapp/, а программа подхватывает локальный - вот и причина. То же самое работает для пропавших библиотек (ищи ENOENT на .so), сертификатов, sock-файлов.

Полезная связка: чтобы поймать сразу и "файл не открылся", и "файл открыли, но путь странный", добавь -e trace=openat,stat,statx (так увидишь и проверки наличия), а флаг -y подпишет, какому файлу соответствует каждый дескриптор в последующих read/write.

Если процесс ещё не запущен, можно сразу трассировать запуск, без attach:

Код: Выделить всё

strace -f -e trace=openat myapp --start 2>strace.log
Маленькая, но важная деталь: strace пишет свой вывод в stderr, а не в stdout. Поэтому перенаправляем через 2> или, лучше, через -o strace.log, иначе он перемешается с выводом самой программы и потеряется.

Современная альтернатива (2026). Если речь именно про "какие файлы открывает приложение", в проде вместо strace часто берут eBPF-инструмент opensnoop из пакетов bcc/bpftrace: он ловит все open/openat по всей системе с накладными расходами около процента и не тормозит цель. Запуск sudo opensnoop-bpfcc -n myapp покажет PID, имя процесса, fd, код ошибки (ERR) и путь - тот же ENOENT, но без риска просадить сервис.

Кейс 3. Программа тормозит: ищем, где утекает время

Программа работает, но медленно, а профайлера под рукой нет. strace отлично показывает, не залипает ли она на системных вызовах. Тут три флага, которые надо знать наизусть.

Сводка -c. Самое мощное для первого взгляда. Запускаем с -c, дожидаемся конца работы (или жмём Ctrl+C при attach) - и strace выдаёт таблицу: сколько времени и вызовов ушло на каждый syscall.

Код: Выделить всё

sudo strace -c -p 12345
^C
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 71.20    0.412233        4041       102           recvfrom
 22.05    0.127700         319       400           write
  4.10    0.023740          11      2100        18  read
  2.65    0.015350           7      2090           futex
------ ----------- ----------- --------- --------- ----------------
100.00    0.579023                  4692        18  total
Читаем по колонкам (порядок и имена точные, проверено по man strace на 2026):
  • % time - доля времени, ушедшая на этот вызов. Верхняя строка после сортировки - твой главный подозреваемый.
  • seconds - суммарное время в этом syscall. Это время в ядре, не настенные часы.
  • usecs/call - среднее время на один вызов в микросекундах.
  • calls - сколько раз вызвали.
  • errors - сколько раз вернулась ошибка.
  • syscall - имя вызова.
В примере 71% времени съел recvfrom - программа в основном сидит и ждёт ответы из сети. Значит, тормозит не она сама, а то, к чему она ходит (база, соседний сервис). А вот если бы вверху висел write с тысячами вызовов по несколько байт - это был бы признак, что код пишет мелкими порциями без буферизации, и лечится это уже в коде. Колонка errors тоже полезна: 18 ошибок на read - отдельный повод копнуть (повторные EAGAIN на неблокирующем сокете - норма для асинхронного кода, а вот EIO - уже железо или ФС). Хочешь отсортировать иначе - есть -S time, -S calls, -S errors; а -w переключает -c на настенное время ожидания вместо процессорного, что для "висящих" вызовов нагляднее.

Тайминги -T и -r. Когда сводка показала виновника, хочется увидеть конкретные медленные вызовы в потоке. Тут помогают strace тайминги:
  • -T дописывает в конце каждой строки длительность вызова в угловых скобках: <0.000456> - это секунды (тут 0.45 мс).
  • -r ставит в начале строки относительное время - сколько прошло с предыдущего syscall.
  • -tt добавляет абсолютную метку времени с микросекундами - удобно сшивать с логами приложения.

Код: Выделить всё

sudo strace -T -p 12345
recvfrom(7, "HTTP/1.1 200 OK"..., 4096, 0, NULL, NULL) = 1480 <0.812043>
write(1, "done\n", 5)                    = 5 <0.000018>
Сразу видно аномалию: recvfrom выполнялся 0.81 секунды - почти секунду программа ждала ответ из сокета 7. А следующий write - 18 микросекунд, мгновенно. Вот он, тормоз: не процессор, не диск, а ожидание сети. Дальше идёшь разбираться с тем, кто на другом конце.

Кто вызвал этот syscall: stack trace без gdb

Частый затык: видишь, ЧТО зовётся, но не понимаешь, ОТКУДА в коде. Раньше тут лезли в gdb. Сейчас у strace есть флаг -k - он печатает стек вызовов после каждого syscall (нужна сборка с libunwind, в современных дистрибутивах она штатная):

Код: Выделить всё

sudo strace -k -e trace=connect -p 12345
connect(9, {sa_family=AF_INET, sin_port=htons(5432), ...}) = -1 EINPROGRESS
 > /usr/lib/libpq.so.5(PQconnectPoll+0x1a) [0x2b1a]
 > /usr/lib/myapp(db_connect+0x44) [0x9c44]
 > /usr/lib/myapp(main+0x210) [0x1210]
Теперь видно не просто "программа куда-то коннектится", а что это идёт из db_connect через libpq - то есть лезет именно в postgres. Это превращает strace из "что делает" в "откуда в коде это растёт". На больших stack trace помни про накладные расходы: -k заметно замедляет, держи его под точечным -e.

Типичные грабли и заблуждения
  • Сам strace замедляет программу - и сильно. Это не мелочь: ptrace-трассировка для горячего по syscall кода замедляет цель примерно на два порядка (в бенчах geteuid strace медленнее eBPF около 100 раз). Поэтому абсолютные тайминги под strace не равны реальным; используй его, чтобы найти, ГДЕ тормозит, а не для точного бенчмарка. На нагруженном проде высокочастотный сервис под strace можно реально уронить по латентности - либо ограничивай через -e нужными вызовами и -DD (отдельный поток трассировщика), либо вообще бери eBPF.
  • Забыл -f и не увидел половину. Многопоточные и форкающиеся программы (nginx, postgres, питон/гоу с воркерами) без -f показывают только главный поток или процесс. Нет ожидаемых строк - первым делом добавь -f.
  • Ищешь вывод в stdout. strace пишет в stderr. Чтобы сохранить в файл, бери -o trace.log или перенаправляй через 2>, иначе grep по выводу ничего не найдёт.
  • "Завис на read - значит баг в read". Нет. read просто ждёт данные, которых нет. Проблема не в нём, а в том, кто должен эти данные прислать. syscall - это симптом, а не причина.
  • Не можешь прицепиться: "Operation not permitted". Часто это ptrace_scope. Проверь cat /proc/sys/kernel/yama/ptrace_scope; если там 1 или выше - цепляйся через sudo либо трассируй процесс от того же пользователя. В контейнере добавь возможность --cap-add=SYS_PTRACE, иначе attach не разрешат.
  • strace молчит и ничего не выводит. Если процесс намертво залип в ядре (например, в неубиваемом состоянии D - непрерываемый сон на диске), новых syscall нет, и strace тоже молчит. Тогда смотри стек ядра напрямую: sudo cat /proc/PID/stack и cat /proc/PID/wchan - они покажут, в какой функции ядра застрял процесс (если там io_schedule или подобное - висим на дисковом или сетевом вводе-выводе внутри ядра, и strace тут бессилен).
Когда strace, а когда eBPF: ориентир на 2026

strace в 2026 никуда не делся и остаётся инструментом номер один, когда надо детально, по-человечески читаемо разобрать ОДИН процесс: повисший сервис, кривой запуск, поиск конфига. Его сила - подробный вывод аргументов и errno.

Но для постоянного наблюдения и для прода стандарт сместился к eBPF. Готовые инструменты из пакетов bcc и bpftrace дают почти ту же информацию с накладными расходами около процента и без остановки цели:
  • opensnoop - какие файлы открываются и с какими ошибками (замена strace -e trace=openat по всей системе).
  • syscount - сводка по частоте syscall и по процессам (живая замена strace -c, безопасная под нагрузкой).
  • execsnoop - кто что запускает, tcpconnect/tcplife - сетевые соединения и их время жизни.
Правило простое: разовый разбор зависшего процесса - strace; непрерывный или высоконагруженный прод - eBPF. perf тут отвечает за профиль CPU (где горит процессор), а strace и eBPF - за то, что происходит на границе с ядром.

Мини-лаба: повтори руками прямо сейчас

Пятиминутная практика, чтобы strace стал родным. Понадобится один терминал.
  • Висим. Запусти sleep 100 &, запомни PID из вывода, потом strace -p PID. Увидишь зависший clock_nanosleep без закрытой скобки - вот так выглядит "процесс ждёт". Останови strace через Ctrl+C, sleep оставь.
  • Ищем файл. Выполни strace -e trace=openat cat /etc/hostname. Найди строку, где cat успешно открывает /etc/hostname (результат - дескриптор 3). Теперь strace -e trace=openat cat /nope/none.txt - и поймай ENOENT на несуществующем пути.
  • Подпись дескрипторов. Повтори первый шаг как strace -yy -e trace=openat,read cat /etc/hostname и убедись, что strace сам подписал путь рядом с fd. Это тот приём, что экономит ходьбу в /proc.
  • Сводка по времени. Запусти strace -c ls -R /usr/share >/dev/null. В таблице найди, на какой syscall ушло больше всего вызовов и времени (скорее всего getdents64 или statx). Это и есть "узкое место" обхода каталогов.
  • Тайминги. Повтори strace -T -c ls -R /usr/share >/dev/null - и сопоставь сводку с тем, что видишь по строкам.
Контрольные вопросы
  • Ты прицепился к зависшему процессу и видишь незавершённую строку futex(..., FUTEX_WAIT...). Чего ждёт программа и с чего начнёшь искать причину?
  • В выводе openat встретилась строка с -1 ENOENT. Что это значит и что конкретно она тебе говорит про конфиг?
  • Какие шесть колонок в выводе strace -c и какая из них первой подскажет, где программа теряет время?
  • strace -p PID прицепился, но не выводит вообще ничего, а процесс висит. Что происходит и чем смотреть дальше?
  • Когда в 2026 ты возьмёшь strace, а когда opensnoop/syscount на eBPF, и почему?
Что запомнить

strace показывает реальный диалог программы с ядром, и почти любой "висит/падает/тормозит" читается по нему за минуты. Висит - смотри незавершённый syscall (futex - лок, read/recvfrom - данные, connect - соединение), подпиши fd через -yy. Не находит файл - -e trace=openat и ищи ENOENT, вот по какому пути промахнулась. Медленно - сначала strace -c для общей картины, потом -T/-r, чтобы поймать конкретные долгие вызовы, и -k, чтобы понять, откуда в коде они растут. Три привычки: добавляй -f для многопоточных, помни про stderr, держи в голове, что syscall - это симптом, а причину ищи на другом конце дескриптора. И главное по 2026: strace - для разового разбора одного процесса, а на проде под нагрузкой бери eBPF (opensnoop, syscount), чтобы не уронить сервис самой диагностикой.
👍4 ❤️2 🔥 😄 🤔1
Аватара пользователя
billiard
Сообщения: 1
Зарегистрирован: 04 июн 2026, 15:53

Re: strace в бою: почему программа висит, падает или тормозит

Сообщение billiard »

Спасибо, наконец дошло зачем -f нужен. Цеплялся к gunicorn без него и удивлялся что strace почти пустой, только мастер-процесс видно. С -f сразу воркеры посыпались.
👍2 ❤️1 🔥 😄 🤔1
Аватара пользователя
enclave
Сообщения: 1
Зарегистрирован: 24 май 2026, 06:50

Re: strace в бою: почему программа висит, падает или тормозит

Сообщение enclave »

Не знал про -yy, всегда руками лазил в /proc/PID/fd. Теперь сразу видно что fd 7 это TCP на 5432, минус две команды. И за блок про eBPF отдельное спасибо, чуть не запустил strace -c на боевом nginx.
👍3 ❤️1 🔥 😄 🤔
Ответить
← Предыдущая глава
strace: трассировка системных вызовов на практике
Следующая глава →
ltrace: трассировка вызовов библиотек

Все главы курса «Диагностика и производительность Linux: логи, strace, perf и eBPF»

Поделиться темой: ✈ Telegram VK
Похожие запросы: strace почему программа висит и тормозитчто такое системный вызов и зачем трассироватькак посмотреть процессы в linux и убить зависшийс чего начать диагностику linux когда сервер тормозит

Вернуться в «Диагностика и производительность Linux: логи, strace, perf и eBPF»

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 1 гость