Ba con số us, sy, wa xuất hiện trong mọi công cụ đo Linux. Bài này đo xem mỗi loại tải chảy vào con số nào — và tìm ra một chỗ wa nói dối.

Ba loại tải với dấu vết riêng, và iowait tụt xuống khi CPU bận

Ba loại tải, ba chỗ thời gian chảy vào

Tải us sy id wa b in cs
Nền 5 2 93 0 0 16.339 24.446
Tính toán thuần 38 2 59 0 0 18.063 18.784
Gọi hệ thống dày đặc 13 26 61 0 0 14.931 13.762
Đọc đĩa trực tiếp 1 2 62 35 6 72.951 106.175

Ba dấu vết rõ rệt:

  • us cao — CPU chạy mã của bạn. Đây là tải "thật", và tối ưu hoá thuật toán sẽ giúp.
  • sy cao — CPU chạy trong nhân. Thường là quá nhiều lời gọi hệ thống nhỏ, hoặc mạng.
  • wa cao — CPU rảnh và đang chờ đĩa.

Cột in (số ngắt) ở dòng cuối đáng chú ý: 72.951 so với 16.339 lúc nền — gấp 4,5 lần. Mỗi lần một thao tác I/O hoàn tất là một ngắt. Đây là tín hiệu phụ tốt để xác nhận nghi ngờ về đĩa.

Nhưng iowait tụt xuống khi CPU bận

Đây là phép đo quan trọng nhất bài.

Tôi giữ nguyên tải đĩa và thêm 14 tiến trình tính toán:

Tình huống us sy id wa b
Chỉ tải đĩa 5 3 56 35 6
Tải đĩa + 14 tiến trình CPU 88 3 5 4 6
CPU hết, còn đĩa 8 4 54 35 6

Cột b bằng 6 ở cả ba dòng — lượng I/O không đổi một chút nào.

Nhưng wa đi từ 35 xuống 4 rồi trở lại 35.

Vì sao: iowait là một phần của idle

iowait không phải "thời gian dành cho I/O". Nó là thời gian CPU không có gì để chạy, và có ít nhất một thao tác I/O đang treo.

Khi CPU có việc khác để làm, thời gian đó được tính vào us — và wa sụp xuống dù đĩa vẫn tắc nghẽn y hệt.

Hệ quả thực hành, và nó ngược với cách phần lớn người ta dùng con số này:

wa thấp không chứng minh hệ thống không nghẽn đĩa.

Trên một máy chủ chạy đầy tải, wa gần như luôn thấp — không phải vì đĩa nhanh, mà vì CPU luôn có việc. Đúng lúc bạn cần nó nhất, chỉ số này im lặng.

Muốn chắc thì nhìn cột b — số tiến trình bị chặn ở I/O. Nó không phụ thuộc CPU có bận hay không. Hoặc nhìn thẳng độ trễ thiết bị bằng iostat -x, phần sau của sê-ri sẽ đo.

sy cao nghĩa là gì

Trong phép đo, 6 tiến trình dd if=/dev/zero of=/dev/null bs=1 đẩy sy lên 26%. Mỗi byte là hai lời gọi hệ thống — readwrite — nên chúng sinh ra hàng triệu lời gọi mỗi giây.

Ba nguyên nhân thường gặp của sy cao trong ứng dụng thật:

  • Đọc/ghi từng byte hoặc từng dòng thay vì theo lô. Đây là phiên bản Linux của bài toán "lượt đi về" trong sê-ri Redis.
  • Nhiều kết nối mạng ngắn. Mỗi accept, read, write, close là một lời gọi.
  • Tranh chấp khoá làm nhiều lời gọi futex.

Cách xác nhận rẻ nhất:

strace -c -p <pid> -f

Nó đếm số lời gọi theo loại. Một hàng đứng đầu với hàng triệu lời gọi là câu trả lời.

Các cột ít được nhắc

vmstat/proc/stat còn vài cột nữa, và hai cái đáng biết:

st (steal) — thời gian CPU ảo bị nhà cung cấp lấy đi để phục vụ máy ảo khác. Trên máy vật lý nó luôn 0. Trên đám mây, st khác 0 kéo dài nghĩa là bạn đang chia sẻ CPU với hàng xóm ồn ào, và không có gì trong ứng dụng của bạn sửa được điều đó.

ni (nice) — thời gian chạy tiến trình đã hạ ưu tiên. Tách khỏi us để bạn phân biệt được công việc nền với công việc chính.

/proc/stat chứa số tích luỹ từ lúc khởi động:

cpu user=68093583 nice=5 system=17692748 idle=1908196406 iowait=2913490

Đơn vị là "jiffies". Mọi công cụ đều đọc file này và tính hiệu giữa hai lần đọc — đó là lý do dòng đầu của vmstat khác hẳn các dòng sau: nó là trung bình từ lúc khởi động.

Quy trình

Từ những gì đo được, thứ tự đọc bốn con số:

  1. st khác 0? → vấn đề ở tầng ảo hoá, không ở bạn.
  2. wa cao? → nghẽn đĩa, chắc chắn.
  3. wa thấp nhưng b > 0 kéo dài? → vẫn nghẽn đĩa, chỉ là CPU bận nên wa không hiện.
  4. sy cao mà us thấp? → quá nhiều lời gọi hệ thống; xem strace -c.
  5. us cao? → tải tính toán thật; đây là lúc profiler có ích.

Bước 3 là bước hay bị bỏ qua nhất, và nó chính là kết quả đo trong bài này.

Thử ba mươi giây

Xem thời gian CPU của hệ thống đang chảy vào đâu, và kiểm chéo với b:

vmstat 1 6 | tail -5 | awk '{
  printf "us=%-3s sy=%-3s id=%-3s wa=%-3s st=%-3s | r=%-3s b=%-3s\n", $13,$14,$15,$16,$17,$1,$2
}'

Rồi với một tiến trình cụ thể, xem tỉ lệ giữa thời gian người dùng và thời gian nhân:

cat /proc/<pid>/stat | awk '{print "utime=" $14 "  stime=" $15}'

stime lớn hơn utime nghĩa là tiến trình đó dành phần lớn thời gian trong nhân — gần như luôn là dấu hiệu của quá nhiều lời gọi hệ thống nhỏ, và gần như luôn sửa được bằng cách gộp thao tác lại.

Phần sau: đọc /proc — nguồn thật của mọi con số ở trên.