分享自己做的一個指定程式以及執行緒長時間cpu監控的工具

良知猶存發表於2021-12-24

前言:

前面給大家分享過一個工作中用到的編譯拷貝指令碼,其實工作中還有一些其他工具的使用,今天再來分享一個自己純手工的CPU監控的指令碼。大家可以結合上篇文章與本篇文章一起學習shell。

主要實現功能:

  • 1.監控指定程式是否執行
  • 2.讀取該程式所在當前CPU的佔用率,5s一次的執行頻率計算當前程式 5分鐘 10分鐘 15分鐘的平均cpu佔用率
  • 3.計算該程式下用PID排序的前十個執行緒的 5分鐘 10分鐘 15分鐘的平均cpu佔用率

作者:良知猶存

轉載授權以及圍觀:歡迎關注微信公眾號:羽林君

或者新增作者個人微信:become_me


情節介紹:

在工作中,我們會對除錯的程式以及執行緒進行效能分析並進行調優,通常我們使用linux下很多的工具包例如,perf 效能分析工具,以及剖析工具 GNU profiler(gprof 可以為 Linux平臺上的程式精確分析效能瓶頸。gprof精確地給出函式被呼叫的時間和次數,給出函式呼叫關係)。

當然現在運維以及自動駕駛裡面工作對效能分析工具熟悉也要很多要求,舉例展示一個自動駕駛相關的系統工程師需要掌握的一些效能分析工具,包含speccpu、fio、iperf、stream、mlc、lmbench、erf、emon/Vtune等工具及相關調優手段,以後有時間再給大家一一介紹這些使用的效能分析工具。

今天呢,沒有過多描述這些工具,因為我遇到的情況是沒有這些工具,所以為了實現一個程式監控,我自己寫了一個指令碼,今天主要給大家分享,如果你工作中,需要一個效能監控的要求,但是你使用的環境中沒有這些工具,此外你的環境支援shell指令碼,那麼這篇文章應該對你有所幫助。

好了,言歸正傳,接下來我給大家分享我寫這些指令碼使用的技術,以及最終實現的情況。

下圖是指令碼執行的流程圖:

指令碼內容:

#!/bin/bash

#一共11個資料 第0個是總的cpu計算 第1-10是執行緒前十個的排序
#             0 1 2 3 4 5 6 7 8 9 a b
cpu_sum_info=(0 0 0 0 0 0 0 0 0 0 0 0)
cpu_5mi_info=(0 0 0 0 0 0 0 0 0 0 0 0)
cpu_10m_info=(0 0 0 0 0 0 0 0 0 0 0 0)
cpu_15m_info=(0 0 0 0 0 0 0 0 0 0 0 0)
cnt=0


function GetPID #User #Name
{                                        

	PsUser=$1                             

	PsName=$2  

	PID=`ps -u $PsUser|grep $PsName|grep -v grep|grep -v vi|grep -v dbx\n|grep -v tail|grep -v start|grep -v stop |sed -n 1p |awk '{print $1}'`

	#echo $PID
	return $PID 

}
function GetCpu
{
	# CpuValue=`ps -p $1 -o pcpu |grep -v CPU | awk '{print $1}' | awk -F. '{print $1}'`
	CpuValue=`top -p $1 -bn 1 | awk 'NR == 8 {print $9}'| awk -F. '{print $1}'`
	# echo "cpu all use "$CpuValue "%"

	return  $CpuValue
}
function SumCpuAverage
{
	sum_value=$1
	cnt=$2
	# echo " "$sum_value $cnt
	((aver=sum_value/cnt))
	# echo "aver="${aver}
	return $aver
}
function float() {
	bc << EOF
num = $1;
base = num / 1;
if (((num - base) * 10) > 1 )
    base += 1;
print base;
EOF
echo ""
return $base
}


while true
do
	date
	GetPID root  exe #修改對應的使用者和程式名

	echo $?

	if [ -n "$PID" -a -e /proc/$PID ]; then
		echo "process exists"
	else                                                                                                       
		exit 0 
	fi

	if ps -p $PID > /dev/null
	then
		echo "$PID is running"
		# Do something knowing the pid exists, i.e. the process with $PID is running
	else
		exit 0
	fi

	GetCpu  $PID 

	single_value=$?
	echo  -e "\033[0;42m cpu used process="  $single_value "% \033[0m"


	((cpu_sum_info[0]=cpu_sum_info[0]+single_value))
	echo "sum 0 = " ${cpu_sum_info[0]}
	echo "all cpu used"   

	# ps -Tp  $PID -o pcpu,pid,lwp | awk 'NR>2{print line}{line=$0} END{print line}'  | sort -rn -k1 | head -n 10
	top -Hp $PID -bn 1 | awk 'NR>8{print line}{line=$0} END{print line}' | sort -rn -k1

	# index=1
	for loop in 1 2 3 4 5 6 7 8 9 10
	do
		var="NR == $((${loop}+8)) {print \$9}"                                                                    
		# echo "loop" $loop $var                                                                            

	# single_value=$(ps -Tp  $PID -o pcpu,pid,lwp| awk 'NR>2{print line}{line=$0} END{print line}' | sort -rn -k1 | head -n 10  | awk "$var" | awk -F. '{print $1}')
	single_value=$(top -Hp $PID -bn 1 | awk 'NR>8{print line}{line=$0} END{print line}' | sort -rn -k1 | awk "$var" | awk -F. '{print $1}')


		# single_value=$(ps -Tp  $PID -o pcpu,pid,lwp| awk 'NR>2{print line}{line=$0} END{print line}' | sort -rn -k1 | head -n 10  | awk "$var")
		# float $single_value
		# single_value=$?                                                                                    
		# cpu_sum_info[$loop] = `expr ${cpu_sum_info[$loop]} + ${single_value}`                                                                

		# ((cpu_sum_info[$loop]+=single_value))                                                                                                
		cpu_sum_info[$loop]=$((cpu_sum_info[$loop]+single_value))                                                                              

		echo "sum" $loop "=" ${cpu_sum_info[$loop]}                                                                                                     
		# let index+=1        		
	done

	((cnt+=1)) 
	echo "cnt = "$cnt
	if [ $((cnt%60)) -eq	0 ]
	then
		for loop in 0 1 2 3 4 5 6 7 8 9 10 
		do
			SumCpuAverage ${cpu_sum_info[$loop]} ${cnt}
			cpu_5mi_info[$loop]=$?
			echo -e "\033[0;41m cpu_5mi_info" $loop "="${cpu_5mi_info[$loop]}	"\033[0m"
		done
		echo -e " " 

	fi
	if [ $((cnt%120)) -eq	0 ]
	then
		for loop in 0 1 2 3 4 5 6 7 8 9 10 
		do
			SumCpuAverage ${cpu_sum_info[$loop]} ${cnt}
			cpu_10m_info[$loop]=$?
			echo -e "\033[0;42m cpu_10m_info="${cpu_10m_info[$loop]} "\033[0m"
		done		

		echo -e " " 

	fi
	if [ $((cnt%180)) -eq	0 ]
	then
		for loop in 0 1 2 3 4 5 6 7 8 9 10 
		do
			SumCpuAverage ${cpu_sum_info[$loop]} ${cnt}
			cpu_15m_info[$loop]=$?
			echo -e "\033[0;43m cpu_15m_info="${cpu_15m_info[$loop]} "\033[0m"
		done		
		echo -e " " 
	fi
	sleep 5
done

以上是monitor.sh 的內容,主要分為幾塊:

通過程式名稱查詢對應該程式的PID:

function GetPID #User #Name
{                                        

	PsUser=$1                             

	PsName=$2  

	PID=`ps -u $PsUser|grep $PsName|grep -v grep|grep -v vi|grep -v dbx\n|grep -v tail|grep -v start|grep -v stop |sed -n 1p |awk '{print $1}'`

	#echo $PID
	return $PID 
}

GetPID root  exe #修改對應的使用者和程式名

這個部分是的核心是一條組合命令,

  • ps -u $PsUser

進行指定使用者的程式查詢

  • grep $PsName

進行指定名稱程式搜尋

  • grep -v grep 等

去除掉grep等其他的搜尋命令的影響

  • sed -n 1p

1p 列印第一行,p 功能為列印
-n 表示靜默模式,一般sed都有把所有讀到的行列印出來,如果不加這個引數,它將一行行列印讀到的,並且由於 1p 會重複列印第一行;

  • awk '{print $1}'

把第一列的引數列印出來

查詢指定程式是否存在:

if [ -n "$PID" -a -e /proc/$PID ]; then
  echo "process exists"
else                                                                                                       
  exit 0 
fi

if ps -p $PID > /dev/null
then
  echo "$PID is running"
  # Do something knowing the pid exists, i.e. the process with $PID is running
else
  exit 0
fi

這裡面使用了proc裡面查詢以及ps命令進行查詢 第一步得到的PID是否存在。
其中 > /dev/null 表示一個黑洞位置,代表linux的空裝置檔案,所有往這個檔案裡面寫入的內容都會丟失,表示不列印輸出資料。

獲得存在的程式的總cpu佔用率:

function GetCpu
{

	CpuValue=`top -p $1 -bn 1 | awk 'NR == 8 {print $9}'| awk -F. '{print $1}'`

	return  $CpuValue
}

GetCpu  $PID 

single_value=$?
echo  -e "\033[0;42m cpu used process="  $single_value "% \033[0m"

重要命令介紹:

  • top -p $1 -bn 1
    使用top命令進行 查詢指定的PID CPU 佔用率資訊,並且只執行一次之後退出(top命令預設是互動模式,無法進行退出,所以此處設定執行之後退出)。

  • awk 'NR == 8 {print $9}'

    這條命令是輸出資料的第8行 第9列,至於為什麼要這麼輸出。這個需要大家去手工去看你需要PID CPU資料的具體行列。這裡我的第8行是指定PID下的CPU詳細資料的一行,所以我輸出了第8行,記住中間的空行也算一行。

這裡我執行了,第9列的輸出,同時也測試了10列的輸出,大家配合輸出資料的不同,也可以得出自己需要的對應那一列。

  • awk -F. '{print $1}'

這個也很熟悉,-F指定分割符號,這裡我選擇了.,去除這個符號的原因是shell指令碼執行浮點計算比較麻煩,需要另寫函式轉換,上面大家應該看到我指令碼里面寫了這個轉換函式,但是我的裝置裡面沒有支援裡面的一下操作特性,所以最終無法實現,只是在PC端實現,在我實際裝置中,我計算的CPU佔用率是去掉小數點的資料,損失了計算精度。

獲得存在的程式的各個執行緒cpu佔用率:



for loop in 1 2 3 4 5 6 7 8 9 10
	do
		var="NR == $((${loop}+8)) {print \$9}"                                                                    
	single_value=$(top -Hp $PID -bn 1 | awk 'NR>8{print line}{line=$0} END{print line}' | sort -rn -k1 | awk "$var" | awk -F. '{print $1}')
                                                                                             
		cpu_sum_info[$loop]=$((cpu_sum_info[$loop]+single_value))                                                                              
		echo "sum" $loop "=" ${cpu_sum_info[$loop]}                                                                                                     
	done

重要命令介紹:

  • top -Hp $PID -bn 1

指定程式下面所有的執行緒列印輸出一次並退出top命令。

  • awk 'NR>8{print line}{line=$0} END{print line}'

配合這張圖大家應該會容易看一些,這個部分是使用 awk命令,把top命令頭資訊去除掉,從第8行開始列印剩餘資料。

  • sort -rn -k1

這個部分是進行排序,其中-r 以相反的順序來排序,-n 依照數值的大小排序,-k1是使用第一排資料進行排列。

var="NR == $((${loop}+8)) {print \$9}"   
awk "$var" 

這個部分因為有特殊字元,所以就用黑色程式碼進行引用。這個部分就是使用awk命令,列印輸出排好序的前10行每一行的執行緒cpu資料。

  • awk -F. '{print $1}'

這個部分參考上一處介紹。

進行變數操作,並進行5分鐘、10分鐘、15分鐘平均值計算

function SumCpuAverage
{
	sum_value=$1
	cnt=$2
	((aver=sum_value/cnt))
	return $aver
}

((cnt+=1)) 
	echo "cnt = "$cnt
	if [ $((cnt%60)) -eq	0 ]
	then
		for loop in 0 1 2 3 4 5 6 7 8 9 10 
		do
			SumCpuAverage ${cpu_sum_info[$loop]} ${cnt}
			cpu_5mi_info[$loop]=$?
			echo -e "\033[0;41m cpu_5mi_info" $loop "="${cpu_5mi_info[$loop]}	"\033[0m"
		done
		echo -e " " 

	fi
	if [ $((cnt%120)) -eq	0 ]
	then
		for loop in 0 1 2 3 4 5 6 7 8 9 10 
		do
			SumCpuAverage ${cpu_sum_info[$loop]} ${cnt}
			cpu_10m_info[$loop]=$?
			echo -e "\033[0;42m cpu_10m_info="${cpu_10m_info[$loop]} "\033[0m"
		done		

		echo -e " " 

	fi
	if [ $((cnt%180)) -eq	0 ]
	then
		for loop in 0 1 2 3 4 5 6 7 8 9 10 
		do
			SumCpuAverage ${cpu_sum_info[$loop]} ${cnt}
			cpu_15m_info[$loop]=$?
			echo -e "\033[0;43m cpu_15m_info="${cpu_15m_info[$loop]} "\033[0m"
		done		
		echo -e " " 
	fi
	sleep 5

這裡主要就是進行儲存變數以及變數進行時間上的平均計算,我選擇了5分鐘、10分鐘、15分鐘的計算。大家也可以選擇自己合適的時間進行分配計算。其中有個sleep 5秒的操作,因為top命令本身也會佔用cpu資源,如果過快查詢,也會導致CPU佔用率很高,所以這個部分休眠時間,大家可以按照自己CPU實際效能進行新增。

最終效果

這是在該程式存在的情況下執行:
這是最開始列印的資訊

這是五分鐘 十分鐘 以及 十五分鐘後計算列印的資料

如果輸入的程式查詢之後沒有,則指令碼直接退出:

結語

這就是我在工作中使用shell自己動手做的一個程式以及該程式包含的執行緒cpu佔用率的監控,適合在大家使用的裝置裡面沒有對應的效能分析工具的應用,大家也可以拿過去直接使用。如果大家有更好的想法和需求,也歡迎大家加我好友交流分享哈。


作者:良知猶存,白天努力工作,晚上原創公號號主。公眾號內容除了技術還有些人生感悟,一個認真輸出內容的職場老司機,也是一個技術之外豐富生活的人,攝影、音樂 and 籃球。關注我,與我一起同行。

                              ‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧  END  ‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧‧

推薦閱讀

【1】在球場上我向人民幣玩家低了頭

【2】Linux開發coredump檔案分析實戰分享

【3】CPU中的程式是怎麼執行起來的 必讀

【4】cartographer環境建立以及建圖測試

【5】設計模式之簡單工廠模式、工廠模式、抽象工廠模式的對比

本公眾號全部原創乾貨已整理成一個目錄,回覆[ 資源 ]即可獲得。

相關文章