由于公司的问题排查需求,需要将公网服务器上占用带宽流量较高的进程或者端口进行记录和保存,以便以后查询问题时可以进行核实。
这个问题纠结了1、2天,使用python应该是可以满足需求,奈何本菜鸟的python实在是拿不出手,所以只能依靠Linux成品的小工具与Shell来实现了。
查找了网上介绍的iftop与nethogs两个工具比较符合要求,但是这两个工具都无法完美的将内容记录下来,如果使用重定向的方式记录内容,那么记录下来的东西并不是文本格式,使用cat查看是没问题的,但是想要进行编辑或者过滤,就不可行了,有兴趣的可以自己试试看。
后来同事发现了iftop的新版本,iftop-1.0pre3(http://freecode.com/projects/iftop),可能需要×××才可以访问,并且安装iftop-1.0pre3需要安装一些依赖,yum与apt-get都没有办法获取到,只能自己去搜索编译了。
CentOS Redhat 系统安装方法(以CentOS 6.x为例):
# wget此步是设置yum源为阿里云,此步可选。 wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-6.repo yum install -y libpcap-devel yum install -y ncurses-devel wget -q http://www.ex-parrot.com/~pdw/iftop/download/iftop-1.0pre3.tar.gz tar xf iftop-1.0pre3.tar.gz cd iftop-1.0pre3 ./configure make && make install
Ubuntu Debian 系统安装方法(以Debian 7.x为例):
# 编辑文件/etc/apt/sources.list(需要sudo),将下面4行内容粘贴到此文件首部。此步是设置apt-get源为阿里云,此步可选。 deb http://mirrors.aliyun.com/debian/ wheezy main non-free contrib deb http://mirrors.aliyun.com/debian/ wheezy-proposed-updates main non-free contrib deb-src http://mirrors.aliyun.com/debian/ wheezy main non-free contrib deb-src http://mirrors.aliyun.com/debian/ wheezy-proposed-updates main non-free contrib yes | apt-get install libpcap-dev yes | apt-get install libncurses5-dev wget -q wget http://www.ex-parrot.com/~pdw/iftop/download/iftop-1.0pre3.tar.gz tar xf iftop-1.0pre3.tar.gz cd iftop-1.0pre3 ./configure make && make install
如果是CentOS、Redhat系统等,执行脚本用sh和bash都一样,但是如果是debian系统,必须使用bash执行脚本,用sh会有问题。
安装好iftop-1.0pre3之后,使用-t参数即可将内容打印到屏幕,此时重定向就可以正常的输出内容。
建议,脚本中不要有中文,我写上中文注释纯粹是怕自己忘了…… 用的时候删掉!
#!/bin/bash # 按照终端显示宽度打印一行短横线 line() { cols=`tput cols` for k in `seq 1 ${cols}` do echo -n "-" done } define() { # 指定工作目录 work_path="/tmp/net_status/" # 获取当前用户名 USER=`whoami` # 获取当前日期和时间 timestamp=`date +%F_%T` # 指定工作目录下以日期为名字的目录 days="${work_path}`date +%F`" iftop_info="${work_path}iftop_info" tidy_info="${work_path}tidy_info" # 指定工作目录下以日期为名字的目录下,以当前的小时为名的文件,没有扩展名。 net_status="${days}/`date +%H`" net1="${work_path}net1" net2="${work_path}net2" net3="${work_path}net3" net4="${work_path}net4" net5="${work_path}net5" net6="${work_path}net6" # 设置iftop收集的默认连接初始条目数为100。 top_num=100 # 用ip a命令显示出本地网卡上所有IP地址,过滤出不是内网地址和本地回环地址的IP。 ipaddr=`ip a | grep "inet" | awk -F '[\/| ]+' '{print $3}' | egrep -v "^192.|^10.|^172.|^127."` if [ ! -z $1 ];then top_num="$1" fi } tidy() { # 使用iftop命令收集eth0网卡连接信息,条目数默认100,可以输入脚本传参1控制条目数,收集10秒信息,以10秒内的带宽平均数排序,将收集到的内容重定向给指定文件。 sudo /usr/local/sbin/iftop -L ${top_num} -s 10 -o 10s -N -P -n -t > ${iftop_info} # 给收集iftop信息的文件授权666,对以公共权限目录作为工作目录的情况来说没有意义。 sudo chmod 666 ${iftop_info} # iftop收集的文件,首3行和尾9行用不到,通过wc -l计数来过滤掉,重定向给指定文件保存。 line_num=`cat ${iftop_info}|wc -l` line_head=`echo $((${line_num}-9))` line_tail=`echo $((${line_head}-3))` cat ${iftop_info} | head -${line_head} | tail -${line_tail} > ${tidy_info} # 收集到eth0网卡全局带宽流量。 total_rate=`cat ${iftop_info} | grep "Total send and receive rate:" | awk '{print $7}'` } check() { # 如果工作目录中已日期为名字的目录不存在,用root权限创建此目录,并将属主设置为当前执行脚本用户。 if [ ! -d ${days} ];then sudo mkdir -p $days sudo chown -R ${USER} ${days} fi } output1() { # 设置计数器m和n,赋予初始值。 m=-1 n=0 # 开始for循环,循环次数为iftop收集条目数。收集iftop统计出的连接的源地址和端口、目的地址和端口、发包带宽占用、回包带宽占用,将这些信息追加重定向到指定文件中保存。 for i in `seq 1 ${top_num}` do m=$(($m+2)) n=$(($n+2)) net_sou=`cat $tidy_info | sed -n "${m},${n}p" | awk 'NR==1{print $2}'` net_des=`cat $tidy_info | sed -n "${m},${n}p" | awk 'NR==2{print $1}'` rate_sou=`cat $tidy_info | sed -n "${m},${n}p" | awk 'NR==1{print $6}'` rate_des=`cat $tidy_info | sed -n "${m},${n}p" | awk 'NR==2{print $5}'` sudo echo "${net_sou} ${net_des} ${rate_sou} ${rate_des}" >> ${net1} done } output2() { # 复制一份指定文件。 cp ${net1} ${net2} # 开始for循环,对指定文件中的所有内容进行字符串比对,如果这些内容中含有关键字“Mb”,则将其前面的数字乘以1024,并将结果在复制的另一份文件中进行批量替换。如果内容中含有关键字“Kb”,只取其前面的数字,并且同样在另一份文件中进行批量替换。如果内容中含有关键字“b”,将其整体改为0,也在另一份文件中进行批量替换。 # 将所有的带宽显示内容转换为Kb单位,如果不足1K,统一标记为0,忽略不计。(不显示单位) for i in `cat ${net1}` do if [[ $i =~ Mb ]];then Mb_sou_num=`echo $i | awk -F 'Mb' '{print $1}'` Mb_fin_num=`echo "${Mb_sou_num}*1024" | bc` sed -i "s#${i}#${Mb_fin_num}#g" ${net2} elif [[ $i =~ Kb ]];then Kb_num=`echo $i | awk -F 'Kb' '{print $1}'` sed -i "s#${i}#${Kb_num}#g" ${net2} elif [[ $i =~ b ]];then sed -i "s#$i#0#g" ${net2} fi done } output3() { # awk数组将相同源地址与源端口的连接合并,并且将对应的带宽占用相加。重定向到指定文件中保存。 awk '{a[$1]+=$3;b[$1]+=$4}END{for(i in a){print i" "a[i]" "b[i];}}' ${net2} > ${net3} # 开始while循环,对指定文件的每一行进行循环,取其发包带宽和回报带宽,将其相加算成双向带宽,在将原本的内容后面追加上双向带宽,追加重定向到指定文件保存。 while read line do lie2=`echo $line | awk '{print $2}'` lie3=`echo $line | awk '{print $3}'` lie4=`echo "${lie2}+${lie3}" | bc` echo "${line} ${lie4}" >> ${net4} done < ${net3} # for循环中嵌套while循环,在保存好的信息中进行过滤,只保留IP地址中包含本地服务器网卡地址的信息,追加重定向到指定文件。 for i in $ipaddr do while read line do if [[ $line =~ $i ]];then echo $line >> ${net5} fi done < ${net4} done } output4() { # 将指定文件按照第4列内容(双向流量)进行由大到小排序。 cat ${net5} | sort -k 4 -rn > ${net6} # 打印空行到最终文件。 echo "" >> ${net_status} echo "" >> ${net_status} # 打印脚本开始时收集的日期和时间到最终文件。 echo "$timestamp" >> ${net_status} # 打印iftop直接统计的eth0网卡双向总带宽占用打印到最终文件。 echo "Total send and receive rate: ${total_rate}" >> ${net_status} line >> ${net_status} # 打印需要显示内容的表头到最终文件。 echo -e "Number \t Address & Port \t Command \t User \t\t Pid \t\t Send \t\t Receive \t Total" >> ${net_status} echo "" >> ${net_status} # 设置计数器,初始值为0。 number=0 # 开始while循环。 while read line do # 计数器加1。 number=`echo ${number}+1 | bc` # 从当前行内容中,取以冒号和空格为分隔符的第2列。 serport=`echo $line | awk -F '[:| ]' '{print $2}'` # 从当前行内容中,取以空格为分隔符的第一列。 souraddr=`echo $line | awk '{print $1}'` # 从当前行内容中,取发包带宽Kb的数字。 Kb_num_send=`echo $line | awk '{print $2}'` # 从当前行内容中,取回包带宽Kb的数字。 Kb_num_receive=`echo $line | awk '{print $3}'` # 从当前内容航中,取双向带宽Kb的数字。 Kb_num_total=`echo $line | awk '{print $4}'` # 取发包、回包、双向带宽数字的整数。 int_Kb_num_send=`echo ${Kb_num_send} | awk -F '.' '{print $1}'` int_Kb_num_receive=`echo ${Kb_num_receive} | awk -F '.' '{print $1}'` int_Kb_num_total=`echo ${Kb_num_total} | awk -F '.' '{print $1}'` # 给发包、回包、双向带宽数字加上Kb单位。 service_send=`echo "${Kb_num_send} Kb"` service_receive=`echo "${Kb_num_receive} Kb"` service_total=`echo "${Kb_num_total} Kb"` # 根据地址端口查询其服务名称(因为时间差问题,有的端口可能已经释放,查不到了) service=`sudo lsof -i:${serport} | tail -1` # 根据取到的信息,分别取出其进程名、pid、运行用户。 service_cmd=`echo ${service} | awk '{print $1}'` service_pid=`echo ${service} | awk '{print $2}'` service_user=`echo ${service} | awk '{print $3}'` # 判断如果进程名取到了,统计进程名长度,如果进程名没有取到,直接将进程名改为一个tab。 if [ ! -z ${service_cmd} ];then length_cmd=`expr length "${service_cmd}"` elif [ -z ${service_cmd} ];then service_cmd=`echo -e "\t"` fi # 判断进程名长度如果小于6,则在后面补上一个tab。 if [ ${length_cmd} -lt 6 ];then service_cmd=`echo -e "${service_cmd}\t"` fi if [ ! -z ${service_user} ];then length_user=`expr length "${service_user}"` elif [ -z ${service_user} ];then service_user=`echo -e " "` fi if [ ${length_user} -lt 6 ];then service_user=`echo -e "${service_user}\t"` fi # 如果发包、回包、双向带宽数字大于1024,将其除以1024,保留两位小数,单位变为Mb。 if [ ${int_Kb_num_send} -gt 1024 ];then Mb_num_send=`echo "scale=2;${Kb_num_send}/1024" | bc` service_send=`echo "${Mb_num_send} Mb"` fi if [ ${int_Kb_num_receive} -gt 1024 ];then Mb_num_receive=`echo "scale=2;${Kb_num_receive}/1024" | bc` service_receive=`echo "${Mb_num_receive} Mb"` fi if [ ${int_Kb_num_total} -gt 1024 ];then Mb_num_total=`echo "scale=2;${Kb_num_total}/1024" | bc` service_total=`echo "${Mb_num_total} Mb"` fi # 统计发包、回包、双向带宽(带单位和空格)长度,如果小于6,后面补上2个空格,因为其最少是4。 length_send=`expr length "${service_send}"` if [ ${length_send} -lt 6 ];then service_send=`echo "${service_send} "` fi length_receive=`expr length "${service_receive}"` if [ ${length_receive} -lt 6 ];then service_receive=`echo "${service_receive} "` fi length_total=`expr length "${service_total}"` if [ ${length_total} -lt 6 ];then service_total=`echo "${service_total} "` fi # 将收集完成的内容,根据需要,进行追加重定向到最终文件中。 echo -e "${number} \t ${souraddr} \t ${service_cmd} \t ${service_user} \t ${service_pid} \t\t ${service_send} \t ${service_receive} \t ${service_total}" >> ${net_status} done < ${net6} line >> ${net_status} echo "" >> ${net_status} echo "" >> ${net_status} } # 删除脚本执行过程中创建的临时使用文件。 del_tmp() { rm -f ${net1} rm -f ${net2} rm -f ${net3} rm -f ${net4} rm -f ${net5} rm -f ${net6} rm -f ${iftop_info} rm -f ${tidy_info} } main() { del_tmp define check tidy output1 output2 output3 output4 del_tmp } main
脚本很冗长,因为赶着用,所以没有进行优化,有兴趣的朋友自行精简,不喜勿喷,谢谢。
还有…… python真的很好用,抓紧学python。
脚本尚有BUG和待优化内容,正式业务不要使用。