由於公司的問題排查需求,需要將公網服務器上佔用帶寬流量較高的進程或者端口進行記錄和保存,以便以後查詢問題時可以進行覈實。
這個問題糾結了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和待優化內容,正式業務不要使用。