top là công cụ ai cũng mở đầu tiên, và cột nào cũng có một cách đọc sai. Bài này đo từng cột.
Hai thang đo %CPU khác nhau
%Cpu(s): 33.3 us, 0.0 sy, 0.0 ni, 50.0 id, 16.7 wa
PID ... S %CPU %MEM COMMAND
4732 R 100.0 1.1 stress-ng
4733 R 100.0 1.1 stress-ng
4727 R 100.0 0.0 stress-ng
4728 R 100.0 0.0 stress-ng
4729 R 100.0 0.0 stress-ng
Năm tiến trình, mỗi cái 100%. Nhưng dòng tổng chỉ nói 33,3%.
Cả hai đều đúng, chỉ khác thang đo:
%CPUcủa tiến trình tính theo một nhân. 100% nghĩa là tiến trình đó đang dùng đầy đúng một nhân. Một tiến trình đa luồng có thể hiện 800%.- Dòng
%Cpu(s)tính trung bình trên tất cả nhân.
Năm nhân bận trên máy 16 nhân là 31% — khớp với 33,3 trong dòng đầu.
Hệ quả: cộng cột %CPU của mọi tiến trình có thể ra 1.600% trên máy 16 nhân, và đó không phải lỗi. Với htop, phím H bật/tắt việc hiển thị từng luồng riêng, và nó đổi hẳn con số bạn nhìn thấy.
VIRT không phải bộ nhớ đang dùng
Một tiến trình ánh xạ 2.048 MB rồi chạm vào từng phần:
chạm 100 MB VIRT = 2.110.580 kB RES = 109.656 kB
chạm 800 MB VIRT = 2.110.580 kB RES = 826.464 kB
VIRT không đổi. RES bám theo phần đã thực sự chạm vào.
VIRT là tổng không gian địa chỉ ảo — mọi thứ tiến trình đã yêu cầu ánh xạ, kể cả phần chưa bao giờ đụng tới. Linux cấp phát lười: một mmap 2 GB không tốn một byte RAM nào cho tới khi bạn ghi vào.
RES (resident set size) là phần thực sự nằm trong RAM. Đây là con số gần nhất với "tiến trình này tốn bao nhiêu bộ nhớ".
Báo động vì VIRT lớn là báo động nhầm. JVM, runtime Go, và mọi thứ dùng mmap đều có VIRT lớn hơn RES nhiều lần — đó là thiết kế, không phải rò rỉ.
Nhưng RES cũng không phải câu trả lời cuối
RES gồm cả bộ nhớ dùng chung: thư viện động, tệp ánh xạ, vùng nhớ chia sẻ. Cột SHR cho biết phần đó.
Nghĩa là cộng RES của mọi tiến trình sẽ ra nhiều hơn RAM thật, vì cùng một thư viện libc được tính cho mọi tiến trình dùng nó. Cột %MEM cũng vậy — tổng của nó có thể vượt 100%.
Muốn con số không trùng lặp thì cần PSS (proportional set size), chia phần dùng chung theo số tiến trình:
grep Pss /proc/<pid>/smaps_rollup
top không hiển thị PSS vì tính nó tốn kém. Nhưng khi cần biết "container này thật sự tốn bao nhiêu", đó mới là con số đúng.
Dòng Tasks đáng đọc
Tasks: 15 total, 6 running, 7 sleeping, 0 stopped, 2 zombie
Ba con số đáng để ý:
running — số tiến trình đang chạy hoặc sẵn sàng chạy. So nó với số nhân: lớn hơn nhiều nghĩa là đang xếp hàng.
zombie — tiến trình đã chết mà cha chưa gọi wait(). Một hai cái là bình thường và thoáng qua. Con số tăng dần nghĩa là có một tiến trình cha không dọn con, và mỗi zombie chiếm một mục trong bảng tiến trình cho tới khi cha chết.
total — tăng đều mà không giảm là dấu hiệu rò rỉ tiến trình, thường tệ hơn rò rỉ bộ nhớ vì nó chạm trần pid_max và làm cả máy không tạo được tiến trình mới.
Cột S — trạng thái
| Nghĩa | |
|---|---|
R |
Đang chạy hoặc sẵn sàng chạy |
S |
Ngủ, ngắt được — chờ mạng, chờ sự kiện |
D |
Ngủ không ngắt được — gần như luôn là chờ đĩa |
Z |
Zombie |
T |
Bị dừng |
D là trạng thái đáng chú ý nhất. Như đo ở phần 2, đó là nhóm làm load average tăng mà CPU không bận. Tiến trình ở D không giết được bằng kill -9 — nó phải chờ I/O xong.
Nhiều tiến trình D kéo dài gần như luôn nghĩa là tầng lưu trữ có vấn đề, không phải ứng dụng.
Ba thứ top không nói
Vì sao tiến trình chậm. top cho biết nó dùng bao nhiêu CPU, không cho biết nó đang làm gì. Cho câu hỏi đó cần strace, perf, hoặc profiler của ngôn ngữ.
Bộ nhớ đi đâu bên trong tiến trình. RES là một con số duy nhất. Muốn biết phần nào là heap, phần nào là stack, phần nào là tệp ánh xạ thì đọc /proc/<pid>/smaps.
Tình trạng của container. top trong container đọc /proc của nhân, nên nó thấy toàn máy. Với ứng dụng chạy trong container, con số đáng tin nằm ở /sys/fs/cgroup/, không ở top.
htop thêm gì
htop hiển thị cùng dữ liệu nhưng dễ đọc hơn ở ba chỗ: thanh CPU từng nhân (thấy ngay tải có đều không), cây tiến trình (F5), và tìm kiếm (F3).
Hai phím đáng nhớ: H ẩn/hiện luồng, và F6 đổi cột sắp xếp. Với ứng dụng nhiều luồng, mặc định của htop liệt kê từng luồng và làm danh sách rất dài — H là phím đầu tiên nên bấm.
Thử ba mươi giây
Xem tiến trình nào thật sự tốn bộ nhớ, không bị VIRT và bộ nhớ dùng chung làm nhiễu:
for p in $(ps -eo pid --no-headers); do
pss=$(awk '/^Pss:/ {s+=$2} END {print s+0}' /proc/$p/smaps_rollup 2>/dev/null)
[ "${pss:-0}" -gt 10000 ] 2>/dev/null || continue
echo "$pss $(tr -d '\0' < /proc/$p/cmdline 2>/dev/null | cut -c1-60)"
done | sort -rn | head -15
Cột đầu là kilobyte PSS — bộ nhớ không trùng lặp. Tổng của nó xấp xỉ đúng RAM đang dùng, khác với tổng RES vốn luôn thổi phồng.
So nó với những gì top sắp xếp theo %MEM: thứ tự thường khác, và với ứng dụng dùng nhiều thư viện chung thì khác rất nhiều.
Phần sau: thời gian người dùng, hệ thống và chờ I/O — mỗi loại tải để lại dấu vết gì.