strace: System Call Tracing for Diagnosing Hangs and Leaks
When a service hangs, standard tools like top, htop, and ps show the state but not the cause. If a process is in state D (uninterruptible sleep), it’s waiting on a syscall. strace attaches to a live process and outputs every system call in real time. This turns a mysterious hang into a specific syscall, its arguments, and return code.
strace uses ptrace, the kernel’s debugging mechanism. On production, tracing slows a process by 2–10x. Use it sparingly, targeting a single PID.
Basic Flags and Syntax
Installation:
Running:
Core flags for diagnostics:
| Flag | Purpose |
|---|---|
-f | Follow child processes |
-c | Count calls and time (summary) |
-tt | Microsecond timestamps |
-T | Time spent in each syscall |
-e trace=openat,read,write | Trace only specified calls |
-e write=1,2 | Trace writes to fd 1 and 2 only |
-o output.log | Write output to file |
-s 1024 | Truncate strings longer than N characters |
Diagnosing Blocking Calls
Scenario: process is hung, ps shows state D. Need to find what it’s blocked on.
Typical output when blocked on a file:
If you see read(...) <time> = 0 with large time values, the process is waiting for data. If <time> is in seconds, you’ve found the bottleneck.
Blocking on epoll_wait, poll, select is normal for an idle process. Look for read, write, openat, sendto with times exceeding 100ms.
For network sockets:
Hang on connect() to an unreachable host:
EINPROGRESS means non-blocking socket, but long time indicates a network issue or timeout.
Finding File Descriptor Leaks
Scenario: process won’t open files, error “too many open files”. Need to find who’s holding the descriptors.
Attach strace with a filter on file operations:
Analyze the output:
Alternative — summary mode:
If close is called less than openat — you found a leak.
With high call frequency (thousands per second), strace generates enormous output. Limit time with -tt and filter by syscall via -e trace=.
Analyzing Slow Requests
Scenario: API endpoint responds in 5 seconds instead of 200ms. Need to find where time is lost.
Find calls with high execution time:
openat with 523ms — investigate: file on NFS, missing permissions, remote filesystem.
For SQL-like queries (PostgreSQL, MySQL), trace the socket:
Slow database query looks like a series of write/read with long time between them:
4.7 seconds between sending the query and receiving data — problem is on the database side or network path to it.
Summary
strace turns a hang with no visible cause into a specific syscall. Attach to PID, filter calls via -e trace=, check execution time with -T. For leaks — compare open/close counts in summary mode. For slow requests — find calls with time exceeding 100ms.
On production use -e trace=write,read,openat instead of tracing all calls — reduces overhead by 3–5x.