Skip to content

Split View: 리눅스 성능 도구 완전 가이드: 출력의 숫자를 해석하는 법

|

리눅스 성능 도구 완전 가이드: 출력의 숫자를 해석하는 법

들어가며

성능 도구를 배우는 것과 성능 도구의 출력을 읽는 것은 다른 일입니다. iostat -x를 칠 줄 아는 사람은 많지만, %util이 100퍼센트라는 결과를 보고 "그래서 이 디스크가 포화인가"에 정확히 답할 수 있는 사람은 적습니다. man 페이지는 그 값이 최신 SSD에서는 포화를 뜻하지 않는다고 명시하고 있는데도 그렇습니다.

이 블로그에는 리눅스 성능 엔지니어링 가이드가 이미 있고, 그 글은 프로파일링 기법과 eBPF, 플레임 그래프, 커널 튜닝을 폭넓게 다룹니다. 이 글은 그와 겹치지 않는 지점을 파고듭니다. 각 도구가 찍는 열 하나하나가 무슨 뜻이며, 어떤 값이 착시를 만드는가가 주제입니다. 도구를 새로 소개하는 글이 아니라, 이미 아는 도구의 출력을 오독하지 않게 만드는 글입니다.

기준 환경은 sysstat 12 이상, 커널 5.x 이상입니다. 열 이름은 sysstat 버전에 따라 바뀌므로(예: 예전의 avgqu-szaqu-sz로 변경) 출력이 다르면 설치된 버전의 man 페이지를 확인하세요.


1. 도구를 고르는 순서 — USE 방법론

무작정 top부터 띄우는 습관을 고치는 가장 쉬운 방법은 USE 체크리스트입니다. 모든 자원에 대해 사용률(Utilization), 포화도(Saturation), 오류(Errors) 세 가지를 묻습니다.

자원사용률포화도오류
CPUmpstat -P ALLvmstatr, pidstat%waitdmesg(MCE)
메모리free -mvmstatsi/so, 페이지 스캔dmesg(OOM)
디스크iostat -xzaqu-sz, awaitdmesg, smartctl
네트워크sar -n DEVnstat의 재전송/드롭ip -s link
파일 핸들/proc/sys/fs/file-nrulimit -n 도달로그의 EMFILE

이 표의 가치는 포화도 열에 있습니다. 사용률만 보면 "CPU 60퍼센트니까 여유 있다"고 판단하지만, 실행 큐가 코어 수의 세 배라면 이미 지연이 발생하고 있습니다. 사용률은 평균이고 포화도는 대기입니다. 사용자가 느끼는 것은 대기 쪽입니다.


2. top과 htop — 착시를 만드는 세 가지 지점

top은 가장 먼저 띄우는 도구지만 오독하기도 가장 쉽습니다.

top -b -n 1 | head -20
top -H -p 1234
top -o %MEM

-b는 배치 모드로 로그에 남기기 좋고, -H는 스레드 단위 표시, -o는 정렬 기준 지정입니다.

착시 1: %CPU는 코어 하나 기준입니다. 8코어 시스템에서 한 프로세스가 800퍼센트를 찍을 수 있습니다. 100퍼센트를 넘는 값을 보고 놀랄 필요가 없습니다. top 실행 중 Shift+I를 누르면 Irix 모드가 꺼지고 전체 코어 기준 값으로 바뀝니다.

착시 2: VIRT는 예약된 가상 주소 공간입니다. 자바나 Go 런타임은 실제로 쓰지 않는 큰 영역을 미리 매핑합니다. 메모리 판단은 RES로, 더 정확하게는 PSS로 해야 합니다.

착시 3: 첫 화면의 %CPU는 부팅 이후 누적값입니다. top을 열자마자 캡처한 값은 의미가 없고, 최소 두 번째 갱신부터 읽어야 합니다.

메모리를 프로세스별로 정확히 보려면 공유 페이지를 안분 계산하는 PSS가 낫습니다.

sudo grep -H '^Pss:' /proc/1234/smaps_rollup

RSS는 그 프로세스가 물리 메모리에 올려 둔 페이지의 총량인데, 여러 프로세스가 공유하는 라이브러리 페이지를 각각 전부 세어 버립니다. 워커 프로세스를 20개 띄우는 웹 서버에서 RSS를 단순 합산하면 실제 메모리 사용량의 몇 배가 나오는 이유가 이것입니다. PSS는 공유 페이지를 참조 프로세스 수로 나눠 배분하므로, PSS의 합은 실제 사용량에 훨씬 가깝습니다. 용량 산정이나 컨테이너 메모리 한도를 정할 때는 반드시 PSS 기준으로 판단하세요.

htop을 쓴다면 표시 옵션에서 스레드 표시와 계층 구조 표시를 켜 두는 편이 좋습니다. 특히 컨테이너 안에서 실행하면 호스트 전체가 아니라 네임스페이스 안의 프로세스만 보이므로, 호스트 관점의 자원 경합을 판단할 때는 호스트에서 다시 확인해야 합니다.


3. vmstat — 시스템 전체를 한 줄로 요약하기

vmstat 1은 성능 도구 중 정보 밀도가 가장 높습니다. man 페이지가 명시하듯 첫 리포트는 마지막 재부팅 이후의 평균값이므로 반드시 둘째 줄부터 읽습니다.

vmstat -w 1 10
vmstat -s
vmstat -d

-w는 넓은 출력, -s는 이벤트 카운터 테이블(반복 없음), -d는 디스크 통계입니다.

읽는 규칙을 정리하면 이렇습니다.

  • r이 코어 수보다 크게, 지속적으로 크면 CPU 포화입니다. 순간적으로 튀는 것은 정상입니다.
  • b가 0이 아니면 I/O 완료를 기다리는 프로세스가 있다는 뜻입니다.
  • si/so가 0이 아니면 스왑이 실제로 일어나는 중입니다. swpd가 크더라도 si/so가 0이면 과거에 밀려 나간 페이지가 그대로 있는 것일 뿐 현재 문제가 아닙니다. 이 구분이 중요합니다.
  • bi/bo는 블록 장치 읽기·쓰기(KiB/s)입니다.
  • cs(컨텍스트 스위치)가 비정상적으로 크면 락 경합이나 과도한 스레드를 의심합니다.
  • in(인터럽트)이 크면 네트워크 트래픽이나 타이머를 봅니다.
  • st는 하이퍼바이저에 빼앗긴 시간입니다. 게스트 안에서는 해결할 수 없는 문제이므로, 이 값이 계속 높으면 인스턴스 타입 변경이나 이전이 답입니다.

ussy의 비율도 유용합니다. syus보다 크다면 애플리케이션 로직이 아니라 시스템 콜, 컨텍스트 스위치, 인터럽트 처리에서 시간을 쓰고 있다는 신호입니다.


4. mpstat과 pidstat — 범위를 좁히는 두 도구

mpstat은 코어별로 나눠 봅니다. 이 도구의 존재 이유는 평균이 감추는 불균형을 드러내는 것입니다.

mpstat -P ALL 1 5

8코어 중 하나만 100퍼센트라면 전체 평균은 12.5퍼센트로 한가해 보이지만 실제로는 단일 스레드 병목입니다. 인터럽트가 한 코어에 몰리는 경우도 여기서 보입니다(%irq, %soft 열).

pidstat은 프로세스 단위 구간 통계입니다. top과 달리 화면을 지우지 않고 계속 찍어 주므로 파일로 남기기에 적합합니다.

pidstat -u 1 5
pidstat -r 1 5
pidstat -d 1 5
pidstat -w 1 5
pidstat -t -p 1234 1 5

각 모드의 열은 다음과 같습니다.

  • -u: %usr, %system, %guest, %wait, %CPU, CPU. 여기서 %wait는 "실행 준비가 되었으나 CPU를 기다린 비율"입니다. 이 값이 높으면 프로세스가 느린 것이 아니라 CPU 경합이 심한 것이므로 대응이 완전히 달라집니다.
  • -r: minflt/s, majflt/s, VSZ, RSS, %MEM. majflt/s(메이저 폴트)가 핵심입니다. 이것은 디스크에서 페이지를 읽어와야 했던 횟수이므로, 0이 아니면 메모리가 부족하거나 파일 캐시가 밀려나는 중입니다. 마이너 폴트는 정상적으로 늘 발생합니다.
  • -d: kB_rd/s, kB_wr/s, kB_ccwr/s, iodelay. kB_ccwr/s는 쓰기가 취소된 양으로, 파일을 썼다가 지운 경우 등에서 나타납니다.
  • -w: 컨텍스트 스위치 통계. 자발적 전환(cswch/s)은 대기 때문이고, 비자발적 전환(nvcswch/s)은 타임 슬라이스 소진 때문입니다. 후자가 많으면 CPU 경합입니다.
  • -t: 스레드 단위로 분해합니다.

5. iostat — await를 보고 %util은 의심하기

iostat -xz 1 5
iostat -xmdz -p ALL 1 3

-x 확장 통계, -z 비활동 장치 생략, -m MiB 단위, -p ALL 파티션까지, -y 첫 리포트 생략입니다.

핵심 열의 정의는 다음과 같습니다.

  • r/s, w/s: 병합 이후 완료된 초당 읽기·쓰기 요청 수.
  • rrqm/s, wrqm/s: 초당 병합된 요청 수. 이 값이 크면 커널이 순차 I/O를 잘 합치고 있다는 뜻입니다.
  • r_await, w_await: 큐 대기 시간을 포함한 평균 서비스 시간(밀리초). 사용자가 체감하는 값에 가장 가깝습니다.
  • aqu-sz: 평균 큐 길이.
  • rareq-sz, wareq-sz: 평균 요청 크기(KiB). 4KiB 근처면 랜덤 I/O, 수백 KiB면 순차 I/O입니다.
  • %util: 장치에 요청이 발행되어 있던 시간 비율.

%util에 대한 man 페이지의 경고는 그대로 인용할 가치가 있습니다. 요청을 직렬로 처리하는 장치에서는 100퍼센트 근처가 포화를 뜻하지만, RAID 배열이나 최신 SSD처럼 병렬 처리하는 장치에서는 이 값이 성능 한계를 반영하지 않습니다. NVMe는 수십 개의 큐를 동시에 굴리므로 %util 100퍼센트에서도 여유가 많습니다.

실무 판단 기준은 이렇게 세우는 편이 안전합니다.

상황해석
await 평소 수준, %util 100퍼센트정상. 장치가 잘 일하는 중
await 급증, aqu-sz 급증진짜 포화
await 급증, aqu-sz 낮음장치 자체가 느려짐(펌웨어, 원격 볼륨, 스로틀링)
r/s+w/s 낮은데 await클라우드 볼륨 IOPS 크레딧 소진 의심

클라우드 환경에서는 마지막 항목이 특히 흔합니다. 버스트 크레딧이 소진되면 IOPS가 갑자기 기준선으로 떨어지면서 요청 수는 적은데 응답이 느려집니다.


6. sar — 지나간 시간을 되감기

성능 문제의 절반은 사람이 로그인했을 때 이미 끝나 있습니다. sar는 그 순간을 되감기 위한 도구입니다.

sar -u -s 03:00:00 -e 04:00:00
sar -r
sar -q
sar -b
sar -n DEV
sar -n EDEV
sar -W
sar -f /var/log/sa/sa14 -u ALL
  • -u CPU, -r 메모리, -q 실행 큐와 부하, -b I/O 전송률, -n DEV 인터페이스 처리량, -n EDEV 인터페이스 오류, -W 스와핑 통계.
  • -s/-e로 시간 구간을, -f로 날짜별 파일을 지정합니다.

수집 파일 경로는 배포판마다 다릅니다. RHEL 계열은 /var/log/sa/, Debian/Ubuntu 계열은 /var/log/sysstat/ 입니다. Debian 계열은 /etc/default/sysstat에서 수집을 켜야 데이터가 쌓이기 시작합니다. 수집 주기와 보존 기간은 /etc/sysstat/sysstat(Debian) 또는 /etc/sysconfig/sysstat(RHEL)에서 조정합니다.

기본 10분 주기는 순간 스파이크를 놓칩니다. 초 단위 이벤트를 잡아야 한다면 수집 주기를 1분으로 줄이거나, 별도의 상시 수집을 붙여야 합니다. 대신 디스크 사용량과 수집 부하가 늘어난다는 점을 감안하세요.


7. 네트워크 — 처리량, 큐, 재전송

처리량은 sar -n DEV, 오류는 sar -n EDEV, 소켓 상태는 ss, 커널 카운터는 nstat입니다.

sar -n DEV 1 5
ip -s link show eth0
nstat -az | grep -i -E 'retrans|listen|prune|collapse'
ss -tin state established | head -20

ss -i는 소켓별 TCP 내부 정보를 보여 줍니다. 여기서 rtt, cwnd, retrans 값을 읽을 수 있습니다. RTT가 크고 cwnd가 작으면 대역폭이 남아도 처리량이 안 나옵니다. 지연-대역폭 곱이 창 크기에 갇힌 전형적인 상황입니다.

nstat의 카운터 중 실무에서 자주 쓰는 것은 다음입니다.

  • TcpExtListenOverflows, TcpExtListenDrops: accept 큐가 넘쳤습니다. 애플리케이션이 accept를 못 따라가거나 백로그가 작습니다.
  • TcpRetransSegs: 재전송. 전체 송신 대비 비율로 봐야 의미가 있습니다.
  • TcpExtTCPRcvCollapsed, TcpExtPruneCalled: 수신 버퍼 압박.

인터페이스 수준 드롭은 ip -s linkRX errors/dropped에서 봅니다. 여기가 늘어나면 커널 위쪽이 아니라 NIC나 링 버퍼를 봐야 합니다.


8. perf — 어느 함수에서 시간을 쓰는가

여기까지 왔는데 "CPU를 쓰는 건 알겠는데 안에서 무엇을 하는지 모르겠다"면 perf 차례입니다.

sudo perf top
sudo perf stat -d -p 1234 -- sleep 10
sudo perf record -F 99 -g -p 1234 -- sleep 30
sudo perf report --stdio | head -40
  • perf top: 실시간으로 가장 많은 CPU를 쓰는 심볼을 보여 줍니다.
  • perf stat: 명령 실행 구간의 하드웨어 카운터를 요약합니다. IPC(instructions per cycle), 캐시 미스, 분기 예측 실패를 봅니다. IPC가 1보다 한참 낮으면 CPU가 메모리를 기다리는 중이라는 강한 신호입니다.
  • perf record -g: 콜 스택을 포함해 샘플을 파일로 남깁니다. -F 99는 초당 99회 샘플링으로, 타이머와 박자가 겹치는 것을 피하려고 100 대신 99를 씁니다.

컨테이너나 클라우드에서 perf가 동작하지 않는다면 커널의 관측 허용 수준 때문일 수 있습니다.

cat /proc/sys/kernel/perf_event_paranoid

값이 2 이하여야 일반적인 프로파일링이 가능하며, 컨테이너에서는 별도의 권한 부여가 필요합니다. 운영 서버에서 이 값을 낮추는 것은 보안 결정이므로 임의로 바꾸지 말고 정책에 따라 처리하세요. 정확한 권한 요건은 사용 중인 배포판과 커널 버전의 문서에서 확인하세요.

심볼이 물음표로만 보인다면 디버그 심볼 패키지가 없는 것입니다. RHEL 계열은 debuginfo 패키지, Debian 계열은 dbgsym 저장소를 추가해야 합니다.


9. 도구 선택 요약표

증상에서 출발해 도구를 고르는 표입니다. 실제로는 이 순서대로 내려가면 대부분 답이 나옵니다.

증상1차 도구확인할 값다음 단계
전체적으로 느림vmstat 1r, wa, si/so, st해당 자원 전용 도구
특정 프로세스만 느림pidstat -u -t%wait, %systemperf record -g
응답 지연이 튐iostat -xz 1r_await, w_awaitpidstat -d
메모리 부족 같음free -m, pidstat -ravailable, majflt/ssmaps_rollup의 PSS
커넥션이 안 맺힘ss -s, nstatListenOverflows백로그와 워커 수 조정
CPU는 노는데 부하만 높음ps D 상태 필터wchan스토리지 계층 조사
과거 시점 재현 불가sar -f해당 시각 구간상시 수집 주기 단축

이 표를 벽에 붙여 두는 것보다 좋은 방법은, 팀에서 실제로 겪은 장애를 한 줄씩 추가해 나가는 것입니다. 조직마다 자주 겪는 병목이 다르기 때문에 그 목록이 훨씬 정확해집니다.

측정에서 흔히 빠뜨리는 마지막 조각은 비교 대상입니다. 지금 응답 시간이 200밀리초라는 사실만으로는 좋은지 나쁜지 알 수 없습니다. 평소 값이 40밀리초였다면 심각한 상황이고, 평소가 220밀리초였다면 아무 일도 아닙니다. 그래서 성능 도구를 도입할 때 가장 먼저 해야 하는 일은 임계치를 정하는 것이 아니라 평상시 값을 며칠간 기록해 기준선을 만드는 것입니다. 기준선이 없으면 모든 숫자가 해석 불가능한 상태로 남습니다.

컨테이너 환경에서는 한 겹이 더 필요합니다. 컨테이너 안에서 본 freenproc은 호스트 값을 그대로 보여 주는 경우가 많고, 실제 제한은 cgroup에 걸려 있습니다. 제한과 실제 사용량은 cgroup v2 파일에서 직접 읽는 것이 확실합니다.

cat /sys/fs/cgroup/memory.max
cat /sys/fs/cgroup/memory.current
cat /sys/fs/cgroup/cpu.max
cat /sys/fs/cgroup/cpu.stat

cpu.stat의 스로틀 관련 카운터가 계속 증가한다면, 호스트 CPU가 한가하더라도 컨테이너는 주기적으로 멈춰 서고 있는 것입니다. 이 경우 호스트에서 관측한 CPU 사용률은 낮게 나오므로, 컨테이너 안의 지연 원인을 호스트 지표만으로는 절대 찾을 수 없습니다.


퀴즈: 실력을 확인해 보세요

퀴즈 1: vmstat에서 swpd가 2GB인데 si와 so는 계속 0입니다. 메모리 문제일까요?

정답: 현재 진행 중인 문제는 아닙니다

설명: swpd는 스왑에 들어가 있는 총량이고, si/so는 초당 스왑 인/아웃 속도입니다. 과거 어느 시점에 밀려 나간 페이지가 그대로 남아 있을 뿐 지금 스와핑이 일어나지 않는다면 성능에 영향이 없습니다. 오히려 잘 쓰지 않는 페이지가 스왑으로 나가고 그 자리를 페이지 캐시가 쓰는 것은 바람직한 상태입니다. 문제는 si/so가 지속적으로 0이 아닐 때 시작됩니다.

퀴즈 2: pidstat -u에서 %wait가 40퍼센트입니다. 이 프로세스에 CPU를 더 주면 해결될까요?

정답: 이 프로세스의 문제가 아니라 시스템 전체의 CPU 경합 문제입니다

설명: %wait는 "실행 준비가 되었는데 CPU를 받지 못하고 기다린 비율"입니다. 즉 이 프로세스는 일할 준비가 되어 있는데 다른 프로세스에 밀린 것입니다. 대응은 이 프로세스를 최적화하는 것이 아니라, 전체 부하를 줄이거나 코어를 늘리거나 스케줄링 우선순위를 조정하는 쪽입니다.

mpstat -P ALL 1 5
pidstat -u 1 5
퀴즈 3: NVMe 디스크의 %util이 100퍼센트로 고정되어 있습니다. 스토리지를 증설해야 할까요?

정답: 아직 판단할 수 없습니다. await와 aqu-sz를 봐야 합니다

설명: iostat man 페이지는 "요청을 병렬로 처리하는 RAID 배열이나 최신 SSD에서는 %util이 성능 한계를 반영하지 않는다"고 명시합니다. NVMe는 다중 큐로 동작하므로 항상 요청이 하나쯤 발행되어 있고, 그 결과 %util이 쉽게 100퍼센트가 됩니다. 판단 기준은 응답 시간입니다.

iostat -xz 1 5

r_awaitw_await가 평소 수준이면 증설할 이유가 없습니다.

퀴즈 4: 8코어 서버의 부하 평균이 4인데 사용자는 느리다고 합니다. 무엇을 확인하나요?

정답: 코어별 분포와 단일 코어 포화 여부를 먼저 확인합니다

설명: 부하 4는 8코어 기준으로 절반이지만, 그 부하가 한 코어에 몰려 있다면 그 코어를 쓰는 요청은 모두 대기합니다.

mpstat -P ALL 1 5
pidstat -t -p <PID> 1 5

mpstat에서 특정 코어만 100퍼센트라면 단일 스레드 병목이며, 코어를 늘려도 해결되지 않습니다. 인터럽트가 한 코어에 몰린 경우라면 %soft 열이 높게 나옵니다.

퀴즈 5: perf stat 결과에서 IPC가 0.3입니다. 무엇을 의미하나요?

정답: CPU가 명령을 실행하지 못하고 메모리를 기다리는 시간이 길다는 뜻입니다

설명: IPC(instructions per cycle)가 낮다는 것은 사이클을 소비하면서도 명령을 적게 처리했다는 의미입니다. 주된 원인은 캐시 미스, 메모리 지연, 분기 예측 실패입니다. 이 경우 알고리즘 자체의 명령 수를 줄이는 것보다 데이터 접근 패턴을 개선하는 쪽(자료 구조 배치, 순차 접근, 캐시 지역성)이 효과적입니다.

sudo perf stat -d -p 1234 -- sleep 10

-d 옵션이 캐시 관련 카운터를 함께 보여 줍니다.

퀴즈 6: 장애가 새벽 3시에 발생했고 아침에 출근했습니다. 어떤 도구부터 쓰나요?

정답: sar로 해당 시간대의 수집 데이터를 되감습니다

설명: 실시간 도구는 이미 지나간 사건에 무력합니다.

sar -q -s 02:50:00 -e 03:30:00
sar -u -s 02:50:00 -e 03:30:00
sar -r -s 02:50:00 -e 03:30:00
sar -n DEV -s 02:50:00 -e 03:30:00
journalctl --since '2026-08-15 02:50' --until '2026-08-15 03:30' -p warning

수집 파일이 없다면 이번 장애로는 아무것도 알 수 없습니다. 그래서 sysstat 수집을 켜 두는 것이 사후 대응이 아니라 사전 준비인 것입니다.


마치며

성능 도구의 출력은 사실을 말해 주지만 결론을 말해 주지는 않습니다. %util 100퍼센트는 사실이고, "디스크가 포화다"는 해석입니다. 그 해석이 장치 종류에 따라 틀릴 수 있다는 것을 아는 것이 도구를 쓰는 능력입니다.

기억할 원칙 세 가지로 정리합니다. 첫째, 사용률보다 포화도를 먼저 본다. 둘째, 평균이 감추는 불균형을 코어별·프로세스별로 쪼개서 본다. 셋째, 지나간 시간을 볼 수 없으면 아무 도구도 소용없으므로 상시 수집을 켜 둔다.


참고 자료


이어서 읽기

A Complete Guide to Linux Performance Tools: How to Read the Numbers in the Output

Introduction

Learning performance tools and reading the output of performance tools are two different things. Plenty of people know how to type iostat -x, but few can answer precisely, when they see %util at 100 percent, "so is this disk saturated?" That holds even though the man page states outright that the value does not mean saturation on a modern SSD.

This blog already has a Linux performance engineering guide, and that article covers profiling techniques, eBPF, flame graphs and kernel tuning broadly. This article digs into the point where it does not overlap. Its subject is what every single column each tool prints actually means, and which values create an illusion. This is not an article that introduces new tools; it is an article that keeps you from misreading the output of tools you already know.

The reference environment is sysstat 12 or later and kernel 5.x or later. Column names change between sysstat versions (for example, the old avgqu-sz became aqu-sz), so if your output differs, check the man page for the version you have installed.


1. The Order in Which to Pick a Tool — The USE Method

The easiest way to break the habit of firing up top first is the USE checklist. For every resource, you ask three questions: Utilization, Saturation, Errors.

ResourceUtilizationSaturationErrors
CPUmpstat -P ALLr in vmstat, %wait in pidstatdmesg (MCE)
Memoryfree -msi/so in vmstat, page scansdmesg (OOM)
Diskiostat -xzaqu-sz, awaitdmesg, smartctl
Networksar -n DEVretransmits/drops in nstatip -s link
File handles/proc/sys/fs/file-nrhitting ulimit -nEMFILE in the logs

The value of this table lies in the saturation column. If you look only at utilization, you conclude "CPU is at 60 percent, so we have headroom", but if the run queue is three times the core count, delay is already happening. Utilization is an average; saturation is waiting. What users feel is the waiting side.


2. top and htop — Three Places That Create an Illusion

top is the first tool you bring up, and also the easiest one to misread.

top -b -n 1 | head -20
top -H -p 1234
top -o %MEM

-b is batch mode, which is good for logging, -H displays per thread, and -o sets the sort key.

Illusion 1: %CPU is measured against a single core. On an 8-core system one process can show 800 percent. There is no reason to be alarmed by values above 100 percent. Pressing Shift+I while top is running turns off Irix mode and switches to values based on all cores.

Illusion 2: VIRT is reserved virtual address space. The Java and Go runtimes map large regions in advance that they never actually use. Memory judgments should be based on RES, or more accurately on PSS.

Illusion 3: the %CPU on the first screen is a cumulative value since boot. The value you capture the instant you open top is meaningless; you have to read from at least the second refresh.

To see memory accurately per process, PSS, which apportions shared pages, is better.

sudo grep -H '^Pss:' /proc/1234/smaps_rollup

RSS is the total amount of pages that process has resident in physical memory, but it counts library pages shared by several processes in full for each of them. That is why simply summing RSS on a web server running 20 worker processes gives a figure several times the real memory usage. PSS divides shared pages by the number of referencing processes and distributes them, so the sum of PSS is far closer to actual usage. When sizing capacity or setting container memory limits, always judge on a PSS basis.

If you use htop, it is a good idea to turn on thread display and tree display in the display options. In particular, when you run it inside a container you see only the processes in that namespace rather than the whole host, so when judging resource contention from the host's point of view you have to check again on the host.


3. vmstat — Summarizing the Whole System in One Line

vmstat 1 has the highest information density among performance tools. As the man page states, the first report is the average since the last reboot, so always read from the second line onward.

vmstat -w 1 10
vmstat -s
vmstat -d

-w gives wide output, -s gives the event counter table (no repetition), and -d gives disk statistics.

The reading rules come out like this.

  • If r is consistently and substantially larger than the core count, that is CPU saturation. Momentary spikes are normal.
  • If b is not zero, it means there are processes waiting for I/O to complete.
  • If si/so are not zero, swapping is actually happening. Even if swpd is large, when si/so are zero it just means pages pushed out in the past are still there and it is not a current problem. This distinction matters.
  • bi/bo are block device reads and writes (KiB/s).
  • If cs (context switches) is abnormally large, suspect lock contention or excessive threads.
  • If in (interrupts) is large, look at network traffic or timers.
  • st is time stolen by the hypervisor. It is a problem you cannot solve from inside the guest, so if this value stays high, changing the instance type or migrating is the answer.

The ratio of us to sy is useful too. If sy is larger than us, it is a sign that time is going into system calls, context switches and interrupt handling rather than application logic.


4. mpstat and pidstat — Two Tools That Narrow the Scope

mpstat breaks things down per core. This tool exists to expose the imbalance that an average hides.

mpstat -P ALL 1 5

If one of eight cores is at 100 percent, the overall average is 12.5 percent and looks idle, but in reality it is a single-threaded bottleneck. The case where interrupts pile onto one core also shows up here (the %irq and %soft columns).

pidstat gives per-process interval statistics. Unlike top, it keeps printing without clearing the screen, which makes it suitable for writing to a file.

pidstat -u 1 5
pidstat -r 1 5
pidstat -d 1 5
pidstat -w 1 5
pidstat -t -p 1234 1 5

The columns for each mode are as follows.

  • -u: %usr, %system, %guest, %wait, %CPU, CPU. Here %wait is "the share of time the process was ready to run but waiting on a CPU". When this value is high the process is not slow — CPU contention is severe, so the response is completely different.
  • -r: minflt/s, majflt/s, VSZ, RSS, %MEM. majflt/s (major faults) is the key one. It counts how often a page had to be read from disk, so if it is not zero, memory is short or the file cache is being pushed out. Minor faults occur constantly under normal operation.
  • -d: kB_rd/s, kB_wr/s, kB_ccwr/s, iodelay. kB_ccwr/s is the amount of writes that were cancelled, which shows up when a file is written and then deleted, among other cases.
  • -w: context switch statistics. Voluntary switches (cswch/s) are due to waiting, and involuntary switches (nvcswch/s) are due to exhausting the time slice. Many of the latter means CPU contention.
  • -t: breaks output down per thread.

5. iostat — Look at await and Be Suspicious of %util

iostat -xz 1 5
iostat -xmdz -p ALL 1 3

-x extended statistics, -z skip inactive devices, -m MiB units, -p ALL down to partitions, -y skip the first report.

The definitions of the key columns are as follows.

  • r/s, w/s: the number of read and write requests completed per second after merging.
  • rrqm/s, wrqm/s: the number of requests merged per second. A large value means the kernel is doing a good job of coalescing sequential I/O.
  • r_await, w_await: average service time in milliseconds, including queue wait time. This is the closest to what the user perceives.
  • aqu-sz: average queue length.
  • rareq-sz, wareq-sz: average request size in KiB. Around 4KiB means random I/O; hundreds of KiB means sequential I/O.
  • %util: the share of time during which requests were issued to the device.

The man page's warning about %util is worth quoting as is. On devices that serve requests serially, close to 100 percent means saturation, but on devices that process in parallel, such as RAID arrays or modern SSDs, this value does not reflect a performance ceiling. NVMe runs dozens of queues at the same time, so there is plenty of headroom even at %util 100 percent.

It is safer to set your practical criteria like this.

SituationInterpretation
await at usual level, %util 100 percentNormal. The device is doing its job well
await spiking, aqu-sz spikingGenuine saturation
await spiking, aqu-sz lowThe device itself got slower (firmware, remote volume, throttling)
r/s+w/s low but await largeSuspect exhausted IOPS credits on a cloud volume

In cloud environments the last item is especially common. When burst credits run out, IOPS suddenly drops to the baseline, so the request count is low while responses are slow.


6. sar — Rewinding Time That Has Already Passed

Half of all performance problems are already over by the time a human logs in. sar is the tool for rewinding to that moment.

sar -u -s 03:00:00 -e 04:00:00
sar -r
sar -q
sar -b
sar -n DEV
sar -n EDEV
sar -W
sar -f /var/log/sa/sa14 -u ALL
  • -u CPU, -r memory, -q run queue and load, -b I/O transfer rates, -n DEV interface throughput, -n EDEV interface errors, -W swapping statistics.
  • Use -s/-e to specify a time range and -f to specify the per-day file.

The collection file path differs by distribution. On the RHEL family it is /var/log/sa/, and on the Debian/Ubuntu family it is /var/log/sysstat/. On the Debian family you have to enable collection in /etc/default/sysstat before data starts accumulating. Collection interval and retention period are adjusted in /etc/sysstat/sysstat (Debian) or /etc/sysconfig/sysstat (RHEL).

The default 10-minute interval misses momentary spikes. If you have to catch second-scale events, reduce the collection interval to one minute or attach a separate continuous collector. Bear in mind that disk usage and collection overhead go up in exchange.


7. Network — Throughput, Queues, Retransmits

Throughput comes from sar -n DEV, errors from sar -n EDEV, socket state from ss, and kernel counters from nstat.

sar -n DEV 1 5
ip -s link show eth0
nstat -az | grep -i -E 'retrans|listen|prune|collapse'
ss -tin state established | head -20

ss -i shows per-socket TCP internals. From it you can read rtt, cwnd and retrans values. If RTT is large and cwnd is small, throughput will not materialize even with bandwidth to spare. This is the classic case of the bandwidth-delay product being trapped by the window size.

Among the nstat counters, the ones used most often in practice are these.

  • TcpExtListenOverflows, TcpExtListenDrops: the accept queue overflowed. Either the application is failing to keep up with accept or the backlog is too small.
  • TcpRetransSegs: retransmits. It is only meaningful as a ratio against total transmissions.
  • TcpExtTCPRcvCollapsed, TcpExtPruneCalled: receive buffer pressure.

Interface-level drops are visible in RX errors/dropped from ip -s link. When these climb, you need to look at the NIC or the ring buffer rather than at anything above the kernel.


8. perf — Which Function Is Spending the Time

If you have come this far and are thinking "I can see it is using CPU, but I do not know what it is doing inside", it is perf time.

sudo perf top
sudo perf stat -d -p 1234 -- sleep 10
sudo perf record -F 99 -g -p 1234 -- sleep 30
sudo perf report --stdio | head -40
  • perf top: shows in real time the symbols using the most CPU.
  • perf stat: summarizes hardware counters over the interval a command runs. Look at IPC (instructions per cycle), cache misses, and branch mispredictions. An IPC well below 1 is a strong signal that the CPU is waiting on memory.
  • perf record -g: writes samples to a file including call stacks. -F 99 samples 99 times per second; 99 is used instead of 100 to avoid falling in step with timers.

If perf does not work in a container or in the cloud, it may be because of the kernel's observability permission level.

cat /proc/sys/kernel/perf_event_paranoid

The value has to be 2 or lower for ordinary profiling to be possible, and containers need separate privileges granted. Lowering this value on a production server is a security decision, so do not change it on your own; handle it according to policy. Check the exact permission requirements in the documentation for the distribution and kernel version you are running.

If symbols show up only as question marks, the debug symbol package is missing. The RHEL family needs the debuginfo package and the Debian family needs the dbgsym repository added.


9. Tool Selection Summary Table

This is a table for choosing a tool starting from a symptom. In practice, working down this order yields the answer most of the time.

SymptomFirst toolValue to checkNext step
Slow across the boardvmstat 1r, wa, si/so, stThe tool dedicated to that resource
Only one process is slowpidstat -u -t%wait, %systemperf record -g
Response latency spikesiostat -xz 1r_await, w_awaitpidstat -d
Looks like memory pressurefree -m, pidstat -ravailable, majflt/sPSS from smaps_rollup
Connections will not establishss -s, nstatListenOverflowsAdjust backlog and worker count
CPU idle but load is highps filtered on D statewchanInvestigate the storage layer
Cannot reproduce a past momentsar -fThe interval around that timeShorten the continuous collection interval

Better than pinning this table to the wall is adding a line for every incident your team has actually experienced. Every organization hits different bottlenecks regularly, so that list ends up far more accurate.

The last piece most often missing from measurement is something to compare against. Knowing only that response time is 200 milliseconds right now tells you nothing about whether that is good or bad. If the usual value was 40 milliseconds this is serious; if the usual value was 220 milliseconds, nothing is happening. That is why the first thing to do when introducing performance tools is not to set thresholds but to record normal values for a few days and build a baseline. Without a baseline every number stays uninterpretable.

Container environments need one more layer. The free or nproc values you see inside a container often report the host's values directly, while the actual limits are enforced by cgroups. Reading the limits and actual usage directly from cgroup v2 files is the reliable approach.

cat /sys/fs/cgroup/memory.max
cat /sys/fs/cgroup/memory.current
cat /sys/fs/cgroup/cpu.max
cat /sys/fs/cgroup/cpu.stat

If the throttling counters in cpu.stat keep increasing, the container is periodically stalling even when the host CPU is idle. In that case the CPU utilization observed on the host comes out low, so you can never find the cause of latency inside the container from host metrics alone.


Quiz: check your understanding

Quiz 1: vmstat shows swpd at 2GB while si and so stay at 0. Is this a memory problem?

Answer: It is not a problem currently in progress

Why: swpd is the total amount sitting in swap, while si/so are the swap in/out rates per second. If pages pushed out at some point in the past are simply still there and no swapping is happening right now, there is no performance impact. If anything, pages that are rarely used moving out to swap so that page cache can take their place is a desirable state. The problem starts when si/so are persistently non-zero.

Quiz 2: pidstat -u shows %wait at 40 percent. Will giving this process more CPU fix it?

Answer: This is not a problem with this process but a system-wide CPU contention problem

Why: %wait is "the share of time the process was ready to run but did not receive a CPU and had to wait". In other words, this process is ready to work but has been pushed aside by other processes. The response is not to optimize this process but to reduce overall load, add cores, or adjust scheduling priority.

mpstat -P ALL 1 5
pidstat -u 1 5
Quiz 3: %util on an NVMe disk is pinned at 100 percent. Should you add storage?

Answer: You cannot judge yet. You have to look at await and aqu-sz

Why: The iostat man page states that on RAID arrays and modern SSDs that process requests in parallel, %util does not reflect a performance limit. NVMe operates with multiple queues, so there is essentially always a request outstanding, and as a result %util easily reaches 100 percent. The criterion is response time.

iostat -xz 1 5

If r_await and w_await are at their usual levels, there is no reason to expand.

Quiz 4: An 8-core server has a load average of 4, yet users say it is slow. What do you check?

Answer: First check the per-core distribution and whether a single core is saturated

Why: A load of 4 is half of 8 cores, but if that load is concentrated on one core, every request that uses that core waits.

mpstat -P ALL 1 5
pidstat -t -p <PID> 1 5

If only a specific core is at 100 percent in mpstat, it is a single-threaded bottleneck and adding cores will not fix it. If interrupts are concentrated on one core, the %soft column comes out high.

Quiz 5: IPC in the perf stat output is 0.3. What does that mean?

Answer: It means the CPU spends a long time waiting on memory instead of executing instructions

Why: A low IPC (instructions per cycle) means few instructions were processed while cycles were consumed. The main causes are cache misses, memory latency and branch mispredictions. In this case, improving the data access pattern (data structure layout, sequential access, cache locality) is more effective than reducing the instruction count of the algorithm itself.

sudo perf stat -d -p 1234 -- sleep 10

The -d option also shows cache-related counters.

Quiz 6: An incident happened at 3 a.m. and you arrived at work in the morning. Which tool do you use first?

Answer: Rewind the collected data for that time window with sar

Why: Real-time tools are powerless against an event that is already over.

sar -q -s 02:50:00 -e 03:30:00
sar -u -s 02:50:00 -e 03:30:00
sar -r -s 02:50:00 -e 03:30:00
sar -n DEV -s 02:50:00 -e 03:30:00
journalctl --since '2026-08-15 02:50' --until '2026-08-15 03:30' -p warning

If there is no collection file, this incident tells you nothing. That is why enabling sysstat collection is preparation in advance rather than a response after the fact.


Closing

The output of a performance tool tells you facts, but it does not tell you conclusions. %util at 100 percent is a fact; "the disk is saturated" is an interpretation. Knowing that this interpretation can be wrong depending on the device type is what it means to be able to use the tool.

Here are three principles worth remembering. First, look at saturation before utilization. Second, split the imbalance an average hides apart per core and per process. Third, continuous collection has to be on, because no tool is any use if you cannot look at time that has already passed.


References


Further reading