CPU 사용률은 낮은데 프로세스가 유독 느리게 느껴질 때가 있다. top이나 ps는 프로세스가 CPU를 얼마나 썼는지는 보여주지만, 그 프로세스가 CPU를 기다리느라 얼마나 시간을 날렸는지, 디스크 I/O나 스왑·메모리 회수 때문에 얼마나 블로킹됐는지는 알려주지 않는다. 커널에는 이런 “대기 시간”을 태스크 단위로 직접 측정하는 delay accounting(delayacct) 기능이 있다. 이 글에서는 delayacct를 활성화하는 방법과, 이를 조회하는 표준 도구인 getdelays 사용법을 정리한다.
delayacct란
delayacct는 태스크가 특정 자원을 기다리느라 실행되지 못한 누적 시간을 커널이 직접 기록하는 기능이다. CPU 사용 시간(utime/stime)이 “실제로 CPU를 쓴 시간”이라면, delayacct는 그 반대편에 있는 “쓰고 싶었는데 못 쓴 시간”을 잰다. 커널 내부적으로는 generic netlink의 TASKSTATS 패밀리를 통해 사용자 공간에 데이터를 전달하며, 커널을 CONFIG_TASK_DELAY_ACCT=y와 CONFIG_TASKSTATS=y로 빌드해야 동작한다. 최근 주요 배포판의 기본 커널은 대부분 이 옵션이 이미 켜져 있다.
추적하는 지표는 여덟 가지다.
| 지표 | 의미 |
|---|---|
| CPU | 실행 가능(runnable) 상태인데 CPU를 배정받지 못해 대기한 시간 |
| IO (blkio) | 태스크가 직접 발행한 동기 블록 I/O 완료를 기다린 시간 |
| SWAP (swapin) | 스왑아웃된 페이지를 다시 읽어오는 동안 대기한 시간 |
| RECLAIM (freepages) | 메모리 회수(reclaim)가 끝나기를 기다린 시간 |
| THRASHING | 페이지 캐시 스래싱으로 인한 대기 시간 |
| COMPACT | direct memory compaction이 끝나기를 기다린 시간 |
| WPCOPY | write-protect copy(COW) 처리를 기다린 시간 |
| IRQ/SOFTIRQ | 인터럽트·softirq 처리로 인한 대기 시간 |
THRASHING, COMPACT, WPCOPY, IRQ/SOFTIRQ 항목은 커널 버전이 올라가며 순차적으로 추가된 것들이라, 오래된 커널에서는 일부 필드가 0으로만 표시되거나 아예 없을 수 있다.
활성화 확인
커널이 delayacct를 지원하도록 빌드됐는지는 /proc/config.gz(또는 /boot/config-$(uname -r))로 확인할 수 있다.
$ zgrep -E "CONFIG_TASK_DELAY_ACCT|CONFIG_TASKSTATS" /proc/config.gz
CONFIG_TASKSTATS=y
CONFIG_TASK_DELAY_ACCT=y
기능 자체는 커널에 컴파일돼 있어도 런타임에 꺼져 있을 수 있다. kernel.task_delayacct sysctl로 켜고 끌 수 있다.
$ sysctl kernel.task_delayacct
kernel.task_delayacct = 0
$ sudo sysctl -w kernel.task_delayacct=1
kernel.task_delayacct = 1
이 값을 켠 시점 이후에 새로 시작된 태스크만 delayacct 데이터가 기록된다. 이미 떠 있는 프로세스는 다시 시작하거나 재부팅해야 통계가 잡힌다. 재부팅 후에도 유지하려면 /etc/sysctl.d/ 아래 설정 파일을 추가한다.
echo "kernel.task_delayacct = 1" | sudo tee /etc/sysctl.d/99-delayacct.conf
sudo sysctl --system
getdelays로 조회하기
delayacct 데이터는 netlink 인터페이스로만 노출되기 때문에, 이를 읽어 사람이 볼 수 있는 형태로 출력해주는 getdelays 도구가 필요하다. 대부분의 배포판은 이 도구를 별도 패키지로 제공하지 않으므로, 커널 소스 트리의 tools/accounting 디렉터리에서 직접 빌드한다.
sudo apt install linux-source build-essential libc6-dev
git clone --depth 1 https://github.com/torvalds/linux.git
cd linux/tools/accounting
make
sudo ./getdelays -v
netlink 소켓을 열고 taskstats 정보를 읽으려면 root 권한이 필요하다. 기본 사용법은 다음과 같다.
getdelays [-dilv] [-t tgid] [-p pid]
-d는 delay 통계를, -i는 I/O 계정 정보를, -l은 사용 가능한 옵션 목록을, -v는 디버그용 상세 출력을 켠다. -p pid로 특정 프로세스를, -t tgid로 스레드 그룹(전통적인 의미의 프로세스) 단위로 집계된 값을 조회한다. 특정 PID의 delay 통계를 한 번 찍어보려면 다음처럼 실행한다.
$ sudo ./getdelays -d -p 2705
print delayacct stats ON
PID 2705
CPU count real total virtual total delay total delay average
71 28000000ns 32436630ns 600096ns 0.008ms
IO count delay total delay average
15 40163017300ns 2677ms
SWAP count delay total delay average
0 0ns 0ms
RECLAIM count delay total delay average
0 0ns 0ms
THRASHING count delay total delay average
0 0ns 0ms
COMPACT count delay total delay average
0 0ns 0ms
WPCOPY count delay total delay average
0 0ns 0ms
IRQ count delay total delay average
0 0ns 0ms
이 프로세스는 CPU를 71번 배정받았는데 그 대기 시간 합은 0.6ms 남짓으로 미미한 반면, I/O는 15번 발생해 총 2.68초를 기다렸다. CPU가 부족한 게 아니라 디스크 I/O 대기가 병목이라는 걸 바로 알 수 있는 대목이다. 특정 프로세스의 파일 I/O 바이트 수 자체가 궁금하면 -i 옵션을 쓴다.
$ sudo ./getdelays -i -p 1
printing IO accounting
linuxrc: read=65536, write=0, cancelled_write=0
getdelays 자체에는 반복 조회 옵션이 없으므로, watch -n1 sudo ./getdelays -d -p <pid>처럼 watch와 조합하면 특정 프로세스의 병목이 시간에 따라 CPU에서 I/O로, 혹은 스왑으로 옮겨가는지도 관찰할 수 있다.
주의사항
root 권한 필요: taskstats netlink 인터페이스는 CAP_NET_ADMIN을 요구하므로 getdelays는 반드시 root(또는 해당 capability를 가진 사용자)로 실행해야 한다.
활성화 이전 태스크는 기록되지 않는다: kernel.task_delayacct=1로 켠 시점 이전에 이미 실행 중이던 프로세스는 데이터가 쌓이지 않는다. 특정 데몬이나 서비스를 분석하려면 delayacct를 먼저 켠 뒤 재시작해야 한다.
단위와 스레드 그룹 합산에 주의: 출력의 시간 단위는 나노초(ns)이고 평균값만 밀리초(ms)로 별도 표기된다. -t로 조회한 값은 스레드 그룹 내 모든 스레드의 delay를 합산한 값이라, 스레드가 많은 프로세스는 개별 스레드(-p) 값과 크게 차이 날 수 있다.
최신 커널에는 delaytop이라는 대안도 있다: getdelays는 프로세스 하나씩 확인하는 용도라 시스템 전체를 훑어보긴 불편하다. 비교적 최근 커널에는 시스템 전반의 PSI(Pressure Stall Information) 정보와 함께, delay가 큰 태스크 상위 N개를 top처럼 실시간으로 보여주는 delaytop 도구가 추가됐다. CPU/IO/IRQ/메모리 delay 기준으로 정렬해 훑어보고 싶다면 getdelays 대신 이쪽을 먼저 확인해볼 만하다.
마무리
delayacct는 “CPU를 얼마나 썼는가”가 아니라 “무엇을 기다리느라 시간을 날렸는가”를 태스크 단위로 직접 보여주는 몇 안 되는 커널 기능이다. CPU 사용률만 보고는 잡히지 않는 I/O 병목이나 메모리 압박을 진단할 때, sysctl로 켜고 getdelays로 찍어보는 것만으로도 원인을 CPU·I/O·스왑 중 어디로 좁혀야 할지 빠르게 판단할 수 있다.