涉及到 iotop, iostat, netstat, sar 命令的使用。

本文全面介绍Linux系统下四种强大的网络与IO监控工具及其实际应用。详细讲解了iotop进程级IO监控、iostat设备IO统计、netstat网络连接分析和sar系统活动报告工具的安装配置、命令选项和常见使用场景。文章最后提供了一个实用脚本,用于自动收集系统网络数据并实现日志轮转,帮助系统管理员高效监测和排查网络性能问题。

1. iotop

类似于 top 命令,用于监控和显示进程级别的磁盘 I/O 使用情况,可以像 top 一样实时刷新显示。

如果系统默认没有安装,需要先安装:

1
sudo yum install iotop

使用格式:iotop [options]

常用的选项 options 有:

1
2
3
4
5
6
7
8
9
10
-o	    仅显示当前正在进行 I/O 操作的进程
-b 批处理模式,不刷新屏幕,直接输出结果,适用于脚本
-n NUM 指定输出的刷新次数(默认无限刷新)
-d SEC 指定刷新间隔,单位为秒(默认 1 秒)
-p PID 只显示特定进程的 I/O 活动
-P 只显示进程,不显示线程
-u USER 只显示属于特定用户的进程的 I/O 使用情况
-q 使 iotop 运行时安静,只输出一次统计信息而不刷新
-k 输出的单位,kbytes/s
-t 显示时间戳

使用场景:

  • 每两秒刷新一次数据,iotop -d 2

  • 查看指定的 PID 的 IO 活动, iotop -p <PID>

  • 批处理,iotop -b -oPt -n 10 > io_stats.log, 每隔 1 秒输出活动进程的 IO 统计信息到指定文件,输出 10 次。

2. iostat

系统的 I/O 数据统计,可以动态的监视磁盘设备的 IO 负载情况

如果系统默认没有安装,需要先安装:

1
sudo yum install sysstat

使用格式:iostat [ options ] [ interval [ count ] ]

常用的选项 options 有:

1
2
3
4
5
6
7
8
-d            输出设备(磁盘)的 IO 统计信息
-c 输出 CPU 的统计信息,与 -d 选项相互排斥
-h 输出 human readable 格式信息
-k/-m 输出的单位,kbytes/s, mbytes/s,默认是块(每块 512 字节)
-x 输出拓展信息,包含更详细的 I/O 指标
-t 显示采集数据时的时间
-p [device] 显示所有设备或指定设备的**分区**统计
-y 忽略自机器启动以来的累计统计数据,而从命名执行开始统计

使用场景:

  • 显示系统每个设备的 IO 统计数据,执行 iostat -d

  • 显示每个设备以及其分区的 IO 统计数据,执行 iostat -p

  • 每隔 1 秒,持续地采集系统设备 IO 的详细指标,执行 iostat -xyt 1

3. netstat

netstat 查看系统中网络相关活动的重要工具,常用于查看 TCP/UDP 连接,socket 活动等等。

如果系统默认没有安装,则需要进行安装:

1
sudo yum install net-tools

常用的选项有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-r, --route              显示路由表
-i, --interfaces 显示所有网卡表,如 `netstat -i`
-I, --interfaces=<Iface> 显示指定的网卡表,如 `netstat -I=eth0`

-s, --statistics 输出网络的统计信息 (like SNMP)
-v, --verbose verbose 输出
-c, --continuous 连续输出模式

-n,--numeric 不解析主机名,直接显示对应的 ip
-e, --extend 显示更多拓展 or 隐藏的信息, 如 `netstat -i -e` 的输出相当于 `ifconfig`
-p, --programs 显示 PID/Program 占用的 socket

-l, --listening 显示正在监听的 socket 活动
-a, --all 显示所有的 socket 活动(默认: connected)

-t, --tcp 显示 TCP 连接活动
-u, --udp 显示 UDP 连接活动

实际场景:

  • 查找哪些服务正在监听哪些 TCP 端口, 使用 -t 选项: netstat -t -ln

  • 查看某个端口或者 socket 正在被哪个进程/服务占用, 使用 -p 选项: netstat -p -tln | grep :80

  • 查看网卡的网络数据统计信息,使用 -s 选项:netstat -s 3, 后面接一个数字 3 表示每 3 秒刷新一次,也意味着进入 --continue 模式。

4. sar

sar 表示 system activity reporter, 也是 Linux 上有用的性能分析工具。

使用格式是: sar [ options ] [ t[ n] ] [-o file]

常用的选项 options 有:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
-b      I/O 和传送速率的统计 [A_IO]
-B 内存分页统计 [A_PAGE]
-d 块设备统计 [A_DISK]
-n { <关键词> [,...] | ALL }
网络统计 [A_NET_...]
关键词有:
DEV Network interfaces
EDEV Network interfaces (errors)
IP IP traffic (v4)
EIP IP traffic (v4) (errors)
TCP TCP traffic (v4)
ETCP TCP traffic (v4) (errors)
UDP UDP traffic (v4)
UDP6 UDP traffic (v6)
SOCK Sockets (v4)
SOCK6 Sockets (v6)
IP6 IP traffic (v6)
EIP6 IP traffic (v6) (errors)
-q [ <关键词> [,...] | PSI | ALL ]
System load and pressure-stall statistics
关键词有:
LOAD Queue length and load average statistics [A_QUEUE]
CPU Pressure-stall CPU statistics [A_PSI_CPU]
IO Pressure-stall I/O statistics [A_PSI_IO]
MEM Pressure-stall memory statistics [A_PSI_MEM]
-r [ ALL ]
内存使用统计 [A_MEMORY]
-S 交换内存使用统计 [A_MEMORY]
-u [ ALL ]
CPU 利用率 [A_CPU]
-v Kernel tables statistics [A_KTABLES]
-W Swapping statistics [A_SWAP]
-w Task creation and system switching statistics [A_PCSW]
-y TTY devices statistics [A_SERIAL]

t 表示采样间隔,n 表示采样次数

-o 表示输出到指定文件,之后还可以通过 -f 指定读取该文件,查看之前记录的统计数据。

实际场景:

  • 排查 CPU 相关的问题,执行 sar -u 1 10sar -q 1 10 每间隔 1 秒采集一次,共采集 10 次。

  • 排查内存相关的问题,执行 sar -r, sar -B, sar -W,也可以加上采集间隔和采集次数。

  • 排查 IO 相关问题,执行 sar -b, sar -u, sar -d,也可以加上采集间隔和采集次数。

注意到这个命令在不同的操作系统或发行版本中可能在选项上有些差异,需要在具体系统中使用 sar --help 来确认。

参考: https://linuxtools-rst.readthedocs.io/zh-cn/latest/tool/sar.html

一个小脚本

之前项目中需要对系统网络情况进行监控,所以写了这个脚本。它实现了将网络统计数据写入日志文件,然后定期对日志文件进行 rotate,丢掉过期的文件(logroate 服务有系统的 crontab job 定期执行)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
#! /bin/bash
set -e

LOGROTATE_IOSTAT=/etc/logrotate.d/iostat
LOGROTATE_IOTOP=/etc/logrotate.d/iotop
LOGROTATE_SAR=/etc/logrotate.d/sar
LOGROTATE_NETSTAT=/etc/logrotate.d/netstat

# todo
# SYSTEMD_PATH=/usr/lib/systemd/system/watcher.service

new_date() {
date '+%Y-%m-%d-%H-%M-%S'
}

function run_iotop() {
# time-stamp, batch, only process
ret=`iotop -tboP -d $interval | tee -a $logger`
if [[ $? -eq 0 ]]; then
echo "done ..." | tee -a $logger
exit
fi
}

run_iostat() {
while true; do
ret=`iostat -t -y -d $interval | tee -a $logger`
if [[ $? -eq 0 ]]; then
echo "done ..." | tee -a $logger
exit
fi
done
}

run_netstat() {
ret=`netstat -s $interval | tee -a $logger`
if [[ $? -eq 0 ]]; then
echo "done ..." | tee -a $logger
echo >&3
exit
fi
}

run_sar() {
while true; do
ret=`sar -n ETCP $interval | tee -a $logger`
if [[ $? -eq 0 ]]; then
echo "done ..." | tee -a $logger
exit
fi
done
}

setup_logroate() {
LOG=$(readlink -f ${1})
LOG_CONF=$2
if [[ ! -f $LOG_CONF ]]; then
cat <<-EOF > $LOG_CONF
$LOG {
daily
create
rotate 31
missingok
copytruncate
dateext
compress
minsize 1M
create 666 root root
}
EOF
fi
}

stop_iostat() {
ps -ef | grep iostat | grep -v grep | grep -v $1 | grep -v tee | awk '{print $2}' | xargs -I{} kill -9 {}
}

stop_iotop() {
ps -ef | grep iotop | grep -v grep | grep -v $1 | grep -v tee | awk '{print $2}' | xargs -I{} kill -9 {}
}

stop_netstat() {
ps -ef | grep netstat | grep -v grep | grep -v $1 | grep -v tee | awk '{print $2}' | xargs -I{} kill -9 {}
}

stop_sar() {
ps -ef | grep sar | grep -v grep | grep -v $1 | grep -v tee | awk '{print $2}' | xargs -I{} kill -9 {}
}

show_usage() {
echo "Usage: $0 [-c | --cmd] [-o | --operate] [-i | --interval] [-f | --log-file] [-h | --help]"
echo "Example: $0 -c iostat -o start -i 3 -f /home/logs/iostat.log "
echo "Example: $0 -c iostat -o stop"
echo "COMMONDS: iotop, iostat, netstat, sar"
}

main() {
if [[ $# -lt 1 ]]; then
show_usage
exit 1
fi

script_name=$0
ARGS=$(getopt -o i:,f:,o:,c:,h --long cmd:,operate:,interval:,log-file:,help, -- "$@")
if [[ $? -ne 0 ]]; then
echo "parse params error."
show_usage
exit 1
fi
eval set -- "${ARGS}"
while true; do
case $1 in
-c | --cmd)
cmd=$2
shift 2
;;
-o | --operate)
operate=$2
shift 2
;;
-i | --interval)
interval=$2
shift 2
;;
-f | --log-file)
logger=$2
shift 2
;;
-h | --help)
show_usage
exit
;;
--)
shift
break
;;
*)
show_usage
exit 2
;;
esac
done

if [[ -z $logger ]]; then
logger=$(new_date)-${cmd}.log
fi
if [[ -z $interval ]]; then
interval=1
fi

# iostat
if [[ $cmd == "iostat" && $operate == "start" ]]; then
setup_logroate $logger $LOGROTATE_IOSTAT
run_iostat &
fi
if [[ $cmd == "iostat" && $operate == "stop" ]]; then
stop_iostat $script_name
fi

# iotop
if [[ $cmd == "iotop" && $operate == "start" ]]; then
setup_logroate $logger $LOGROTATE_IOTOP
run_iotop &
fi
if [[ $cmd == "iotop" && $operate == "stop" ]]; then
stop_iotop $script_name
fi

# netstat
if [[ $cmd == "netstat" && $operate == "start" ]]; then
setup_logroate $logger $LOGROTATE_NETSTAT
run_netstat &
fi
if [[ $cmd == "netstat" && $operate == "stop" ]]; then
stop_netstat $script_name
fi

# sar
if [[ $cmd == "sar" && $operate == "start" ]]; then
setup_logroate $logger $LOGROTATE_SAR
run_sar &
fi
if [[ $cmd == "sar" && $operate == "stop" ]]; then
stop_sar $script_name
fi

}

main "$@"