最近一直在學(xué)習(xí) RRDtool,但用了一段時間之后發(fā)現(xiàn) RRDtool 提供的 fetch 操作不是很方便,所以萌發(fā)了自己寫一個 fetch 的念頭
并于今天寫完了。主要是提供一個交互式的界面,便于用戶快速的得到自己想要的數(shù)據(jù),或者保存 fetch 的結(jié)果。同時對 fetch 的輸出進(jìn)行
了一些修改,把 timestamp 改為 “年-月-日 小時:分:秒” 的格式,便于閱讀。
本來想把 graph 操作也一起做了,但分析后覺得 graph 的選項(xiàng)太多了,一時半會還不知道以什么方式做好了,所以就留待后續(xù)了。
本人剛學(xué) shell 編程不久,所以如果有錯誤或者需要改進(jìn)的地方,請多多包涵,不吝賜教 ^_^ 。
注 :如果不知道什么是 RRDtool ,請前往 http://bbs./viewthread.php?tid=864861&extra=page%3D1
[color=Navy]
###################################################################################################
# Name :exfetch.sh
#
# Version :v1
#
# Last Modified :2006/12/10 21:09
#
# 一、該腳本目的是為 RRDtool 的 fetch 操作提供一個友好的界面 , 使用戶能夠方便的從 RRD 文件中提取數(shù)據(jù)。
#
# 同時還集成了 info、last、first 操作,目前打算集成 graph 操作。 不過腳本目前不支持 AT-風(fēng)格的時間
#
#
# 二、原來的 fetch 操作有幾個不是很方便的地方 :
#
# a)經(jīng)常需要接觸到 timestamp 格式,例如 fetch 輸出第一列,很不方便,如果可以轉(zhuǎn)換為普通格式的時間最好。
#
# b)需要對 --start / --end 進(jìn)行算術(shù)運(yùn)算,使其符合一定的標(biāo)準(zhǔn),比較麻煩。
#
# c)缺少智能,如果用戶給出的 --start/--end 和 -r 不匹配,fetch 不會報錯,而是
#
# 自動使用更加合適的 resolution ,對于初學(xué)者可能容易造成困惑。
#
# d)經(jīng)常要用到 info、first、last 操作,無法在一個命令中完成這些操作
#
#
#
# 三、該腳本就是要解決上述的這些問題,讓腳本引導(dǎo)用戶輸入條件,再經(jīng)過內(nèi)部判斷,給出候選 RRA,
#
# 并讓查看 RRD 文件信息-》數(shù)據(jù)提取-》數(shù)據(jù)保存 這個過程集成起來。至于繪圖部分,由于考慮到選項(xiàng)太多,所以暫時沒有做,日后會考慮的。
#
####################################################################################################
####################################################################################################
#
# 該腳本用到的 exit status 有如下幾種
#
# 1)RRD 文件不存在 : 1
#
# 2)RRD 文件存在但不具備讀取權(quán)限 :2
#
# 3)結(jié)束時間大于開始時間 :3
#
# 4)沒有符合要求的 RRA :4
#
# 5)收到信號(INT、TERM、QUIT、KILL)
#
####################################################################################################
[/color]
下面附上幾張截圖 :

[ 本帖最后由 ailms 于 2006-12-11 09:28 編輯 ]
ailms 回復(fù)于:2006-12-10 23:08:24
#!/bin/bash
# 定義一個函數(shù),用于收到 INT、TERM、QUIT、KILL 信號時清理臨時文件
function cleanup () {
# 定義臨時文件
final_output="/tmp/fetch.out"
temp_output="/tmp/rrd_file_info_output"
fetch_output="/tmp/fetch.tmp"
header_output="/tmp/header.out"
date_output="/tmp/date.out"
value_output="/tmp/value.out"
paste_output="/tmp/paste.out"
rm -f $final_output $temp_output $fetch_output $header_output $date_output $value_output $paste_output >/dev/null 2>&1
exit 5
}
# 捕捉 INT、TERM、QUIT 、KILL 信號
trap cleanup INT TERM QUIT KILL
#////////////////////////////////////////////////////////
#
# 下面開始主要流程
#
#/////////////////////////////////////////////////////////
echo && read -p "請輸入 RRD 文件的名稱: " rrd_file_name
# 判斷 RRD 文件是否為空或者 RRD 是否存在
if [ -z $rrd_file_name ] || [ ! -f $rrd_file_name ] ;then
echo "ERROR:: 文件名為空,或者RRD 文件不存在"
exit 1;
fi
# 檢查是否對該 RRD 文件具備讀權(quán)限
if [ ! -r $rrd_file_name ]; then
echo "ERROR:: 你沒有訪問該 RRD 文件的權(quán)限 !!"
exit 2;
fi
# 定義臨時輸出文件
final_output="/tmp/fetch.out"
temp_output="/tmp/rrd_file_info_output"
fetch_output="/tmp/fetch.tmp"
header_output="/tmp/header.out"
date_output="/tmp/date.out"
value_output="/tmp/value.out"
paste_output="/tmp/paste.out"
rrdtool info $rrd_file_name > $temp_output
# 找出該 RRD 文件的版本。注意,該值不同于 RRDtool 的版本
rrd_file_ver=$(grep 'rrd_version = ' $temp_output | cut -d '=' -f 2|tr -d \"|tr -d '[:blank:]')
echo "文件 : [ $rrd_file_name ] , 文件版本 : [ $rrd_file_ver ]"
# 找出該 RRD 文件的 step
rrd_file_step=$(grep 'step = ' $temp_output |cut -d '=' -f 2 |tr -d '[:blank:]')
echo && echo "Step : [ $rrd_file_step ]"
# 找出該 RRD 文件的最后更新時間
rrd_file_last_update=$(grep 'last_update = ' $temp_output |cut -d '=' -f 2|tr -d '[:blank:]')
echo && echo "最后更新 : [" $(date -d "1970-01-01 $rrd_file_last_update sec utc" '+%Y/%m/%d %H:%M:%S') "]"
# 找出該 RRD 文件中有多少個 DS
# 注 :原來的腳本有誤,應(yīng)該是除以7,不是除以2,請注意
rrd_file_ds_num=$(($(grep 'ds\[.\+\]' $temp_output -c )/7))
echo && echo "DS 數(shù)量 : [ $rrd_file_ds_num ]"
# 并得出這些 DS 的名稱 (DSN)
rrd_file_ds_name=$(grep 'ds\[.\+\]' $temp_output |cut -d '.' -f 1|sort -u)
# 輸出 ds 部分信息的表頭
echo && echo '編號------名稱------------------類型------------------最小值------------------最大值------------------'
no=0
for i in $rrd_file_ds_name; do
no=$((no+1))
ds_name=$i
ds_name=$(echo $ds_name|cut -d '[' -f 2 |cut -d ']' -f 1) # 取出一個 DS 的名稱
# 取出該 DS 的類型(DST)
ds_type=$(grep "ds\[$ds_name\].type = " $temp_output|cut -d '=' -f 2 |cut -d \" -f 2)
# 取出該 DS 的最小值
ds_min=$(grep "ds\[$ds_name\].min = " $temp_output |cut -d '=' -f 2|cut -d \" -f 2)
# 取出該 DS 的最大值
ds_max=$(grep "ds\[$ds_name\].max = " $temp_output |cut -d '=' -f 2|cut -d \" -f 2)
printf "%-10d" $no
printf "%-22s" $ds_name
printf "%-22s" $ds_type
printf "%-24s" $ds_min
printf "%-24s" $ds_max
echo
done
# 下面開始輸出 RRA 的相關(guān)信息
# 下面的語句找出該 RRD 文件中的 RRA 數(shù)量
rrd_file_rra_num=$(grep 'rra\[[0-9]\+\]' $temp_output |cut -d '.' -f 1 |sort -u|wc -l|tr -d '[:blank:]')
echo && echo "RRA 數(shù)量 : [ $rrd_file_rra_num ]"
echo
# 下面開始輸出 RRA 部分信息的表頭
echo "編號------統(tǒng)計類型--------------每CDP含PDP數(shù)量--------解釋度------------------行數(shù)--------------------起始時間----------------------"
for ((i=0;i<$rrd_file_rra_num;i++));do
printf "%-10d" $i
# 取出該 RRA 的類型 (CF)
rra_type=$(grep "rra\[$i\].cf = " $temp_output |cut -d '=' -f 2|tr -d '[:blank:]'|tr -d \")
# 把當(dāng)前 RRA 的 CF 值放入數(shù)組 rra_type_array 中,后面在判斷用戶輸入的 CF 是否存在時要用到
rra_type_array[$i]=$rra_type
printf "%-22s" $rra_type
# 找出每個 CDP 由多少個 PDP 組成
pdps_per_cdp=$(grep "rra\[$i\].pdp_per_row = " $temp_output |cut -d '=' -f 2 |tr -d '[:blank:]')
printf "%-22s" $pdps_per_cdp # 打印 RRA 中每個 CDP 由多少個 PDP 統(tǒng)計得出
# 得出當(dāng)前 RRA 的解釋度(Resolution)
rra_res=$((rrd_file_step * pdps_per_cdp))
# 把當(dāng)前 RRA 的 resolution 放入 rra_res_array 數(shù)組中,后面在查找合適的 RRA 時要用到
rra_res_array[$i]=$rra_res
printf "%-25s" $rra_res
# 得出該 RRA 的行數(shù)
rra_rows=$(grep "rra\[$i\].rows = " $temp_output |cut -d '=' -f 2|tr -d '[:blank:]')
printf "%-22s" $rra_rows
# 得出當(dāng)前RRA第一個記錄的時間戳
rra_first=$(rrdtool first $rrd_file_name --rraindex $i)
# 轉(zhuǎn)換為具體的時間
rra_first_time=$(date -d "1970-01-01 $rra_first sec utc" '+%Y-%m-%d %H:%m:%S')
printf "%-19s" $rra_first_time
# 得出該 RRA 的時間覆蓋范圍,也就是該 RRA 總共包含的秒數(shù)
rra_time_range=$(($rra_res * $rra_rows))
# 把該 RRA 的時間覆蓋范圍存入 time_range 數(shù)組中,后面在查找合適的 RRA 時要用到
time_range[$i]=$rra_time_range
echo
done
echo
# 下面提示用戶輸入起始時間,默認(rèn)為1天前的這個時刻
read -p "起始時間 (YYYY-MM-DD HH:mm:ss) :" fetch_start
# 檢查用戶輸入的時間是否有效,如果無效則返回重新輸入,是則轉(zhuǎn)換為 timestamp 的格式
while true ; do
if [ -z "$fetch_start" ];then # 如果用戶輸入的時間為空,則默認(rèn)為1天前
fetch_start_timestamp=$(date -d '1 days ago' +%s)
echo && echo -en "\t默認(rèn)開始時間 : "
date -d "1970-01-01 $fetch_start_timestamp sec utc" '+%Y-%m-%d %H:%M:%S'
break; # 跳出開始時間的處理部分
fi
if [ ! -z "$fetch_start" ];then # 如果輸入的時間不為空
fetch_start_timestamp=$(date -d "$fetch_start" +%s) # 嘗試把輸入轉(zhuǎn)換為時間戳
if [ ! -z "$fetch_start_timestamp" ]; then # 如果轉(zhuǎn)換成功,則跳出開始時間處理部分
break;
fi
fi
# 如果輸入不為空,且時間無效,則會重新提示輸入
read -p "輸入的時間無效,請重新輸入 :" fetch_start
done
# 下面提示用戶輸入結(jié)束時間
echo
read -p "結(jié)束時間 (YYYY-MM-DD HH:mm:ss):" fetch_end
# 檢查用戶輸入的時間是否有效,邏輯思路同上
while true ; do
if [ -z "$fetch_end" ];then # 如果輸入為空,則默認(rèn)的結(jié)束時間是當(dāng)前
fetch_end_timestamp=$(date +%s)
echo && echo -en "\t默認(rèn)截止時間: "
date -d "1970-01-01 $fetch_end_timestamp sec utc" '+%Y-%m-%d %H:%M:%S'
break # 不并跳出結(jié)束時間處理部分
fi
if [ ! -z "$fetch_end" ];then # 如果輸入不為空
fetch_end_timestamp=$(date -d "$fetch_end" +%s ) # 嘗試轉(zhuǎn)換為時間戳
if [ ! -z "$fetch_end_timestamp" ]; then # 如果時間轉(zhuǎn)換成功,則跳出結(jié)束時間處理部分
break;
fi
fi
read -p "輸入的時間無效,請重新輸入 :" fetch_end
done
# 接下來檢查 fetch_end 是否大于 fetch_start ,如果不是則報錯
if (( fetch_end_timestamp <= fetch_start_timestamp )) ; then
echo "ERROR:: 結(jié)束時間必須大于開始時間"
exit 3
fi
# 接下來提示用戶輸入想要的統(tǒng)計類型(平均值、最大值、最小值、當(dāng)前值)
cf_type_list="最大值 最小值 平均值 當(dāng)前值"
PS3="
請選擇一種類型 : " # 修改默認(rèn)的 select 提示字符串
echo && echo "請選擇你想要的統(tǒng)計類型 : " && echo
select i in $cf_type_list ; do
case $i in
"最大值") select_cf="MAX" ;break ;;
"最小值") select_cf="MIN" ;break ;;
"平均值") select_cf="AVERAGE" ;break ;;
"當(dāng)前值") select_cf="LAST" ;break ;;
*) echo "無效選擇"
esac
done
# 接下來是選擇合適的RRA,選擇的根據(jù)是兩方面 :
###################################
#
# 第一是該 RRA 的 CF 必須等于用戶指定的 CF
#
# 第二是該 RRA 的時間覆蓋范圍必須大于等于用戶給出的范圍
#
# 第三是該 RRA 的起始時間必須早于用戶給出的起始時間
#
###################################
# 下面該行用于輸出用戶輸入的起始/結(jié)束時間相差的時間范圍(timestamp格式)
fetch_time_range=$((fetch_end_timestamp - fetch_start_timestamp))
# 下面開始按照上面的規(guī)則對每個 RRA 進(jìn)行判斷
for ((i=0;i<rrd_file_rra_num;i++)); do
# 首先要判斷當(dāng)前 RRA 的 cf 類型是否符合用戶的要求,如果不是,直接跳到下一個 RRA
# 下面從 rra_type_array 數(shù)組中取出當(dāng)前RRA 的 cf 類型,數(shù)組的 index 等于 RRA 的 index
rra_type=$(echo ${rra_type_array[$i]})
if [ "$rra_type" != "$select_cf" ]; then # 如果該 RRA 的 CF 和 用戶選擇($select_cf)的不一樣
continue # 則跳出該次循環(huán),開始下一個 RRA 的測試
fi
# 如果當(dāng)前 RRA 的 CF 符合用戶選擇的類新( $select_cf )則判斷時間覆蓋范圍是否和上面的第2,3點(diǎn)
# 下面從 time_ranges 數(shù)組中取出當(dāng)前 RRA 的時間覆蓋范圍
rra_time_range=$(echo ${time_range[$i]})
# 下面首先判斷當(dāng)前 RRA 的時間覆蓋范圍是否大于用戶的要求,如果不是則立即跳過該 RRA
if (( $rra_time_range >= fetch_time_range ));then # 如果當(dāng)前 RRA 的時間覆蓋范圍大于等于用戶指定的范圍,就看是否滿足第3點(diǎn)
rra_first=$(rrdtool first --rraindex $i $rrd_file_name) # 取出當(dāng)前 RRA 的起始時間
if (($fetch_start_timestamp >= $rra_first));then # 如果當(dāng)前 RRA 的起始時間早于用戶指定的起始時間
rra_time_range_ok_list="$rra_time_range_ok_list RRA[$i]" # 則把該 RRA 加入到合適的 RRA 的列表中
fi
fi
done
# 到此已經(jīng)對全部 RRA 進(jìn)行了篩選了,但不保證一定至少有一個合適的 RRA 被選中。
if [ -z "$rra_time_range_ok_list" ];then # 如果 ok 列表為空,說明沒有一個合適的 RRA
echo && echo "對不起,此次操作沒有符合指定要求的 RRA ,請檢查所輸入的要求是否合理"
exit 4
else
echo && echo "此次可供選擇的 RRA 有 : " && echo # 否則輸出可供選擇的 RRA 有那些
fi
# 下面開始提示用戶從合適的 RRA 中選擇一個
# 修改默認(rèn)的 select 提示信息
PS3="
請選擇一個 RRA : "
# 顯示一個列表讓用戶選擇
select select_rra in $rra_time_range_ok_list ; do
if [ ! -z $select_rra ] ; then
# 如果用戶選擇了一個 RRA ,則從 rra_res_array 數(shù)組中取出它的 resolution
rra_chose=$(echo ${select_rra/'RRA['/})
# 從 RRA 名稱中得出該 RRA 的 index 編號,也就是 [ ] 中的數(shù)字
rra_chose=$(echo ${rra_chose%']'})
# 從 rra_res_array 數(shù)組中取出被選擇的 RRA 的解釋度
rra_res=$(echo ${rra_res_array[$rra_chose]})
#echo "該 RRA 的 resolution 是 : $rra_res" # 并顯示該值
# 對起始時間進(jìn)行取整運(yùn)算,保證它剛好是指定的解釋度的整數(shù)倍
fetch_start_timestamp=$(((fetch_start_timestamp)/$rra_res*$rra_res-$rra_res))
# 對截止時間進(jìn)行取證運(yùn)算,保證它是指定的解釋度的整數(shù)倍,這點(diǎn)是必須的,否則 90% 以上的機(jī)率不會得到你想要的結(jié)果
fetch_end_timestamp=$(((fetch_end_timestamp)/$rra_res*$rra_res))
cmd="rrdtool fetch --start $fetch_start_timestamp --end $fetch_end_timestamp -r $rra_res $rrd_file_name $select_cf"
# 詢問是否需要保存文件,由用戶輸入目標(biāo)位置。默認(rèn)不保存。
# 如果不保存,則輸出到屏幕上。并對 fetch 的第一列進(jìn)行處理,換成普通日期的格式,便于查看。
echo && read -p "是否把數(shù)據(jù)保存到文件? [Y/N] : " save_file
if [ -z "$save_file" ] || ( [ "$save_file" != "Y" ] && [ "$save_file" != "y" ] ); then
eval $cmd > $fetch_output # 先把 fetch 的結(jié)果輸出到臨時文件
# 取出 fetch.out 的 標(biāo)題部分,后面會用到
head -n 2 $fetch_output > $header_output
# 取出 fetch 結(jié)果的第一列(時間戳),送入變量 timestamp
timestamp=$(tail -n +3 $fetch_output |cut -d ':' -f 1 )
# 取出 fetch 結(jié)果中的數(shù)值部分,放入文件 /tmp/value.out
tail -n +3 $fetch_output |cut -d ":" -f 2- |tr ' ' "\t" > $value_output
# 對每個時間戳變換為具體時間,格式為 ‘年-月-日 小時:分:秒",結(jié)果寫入 date.out 文件
for i in $timestamp; do
date -d "1970-01-01 $i sec utc" '+%Y-%m-%d %H:%M:%S' >> $date_output
done
# 把前面的 value.out 和現(xiàn)在的 date.out 合并成一個文件
paste $date_output $value_output > $paste_output
cat -n $header_output $paste_output |more
else
eval $cmd > $final_output
chmod 600 $final_output
echo "結(jié)果保存于 $final_output"
ls -l $final_output
fi
break;
else
echo "無效選擇" # 如果用戶輸入的 RRA 編號無效,則給出出錯信息,重新選擇
fi
done
# 下面刪除臨時文件
rm -f $temp_output $fetch_output $date_output $value_output $header_output $paste_output
[ 本帖最后由 ailms 于 2006-12-14 22:01 編輯 ]
lovegqin 回復(fù)于:2006-12-11 07:44:00
早上一來就看如經(jīng)此好的貼子
今天的運(yùn)氣不錯!?。? 頂?。。?!
ailms 回復(fù)于:2006-12-11 09:27:30
不好意思,第一個圖的 “DS數(shù)量”那里錯誤了,(應(yīng)該是2,不是7)腳本已經(jīng)重新改了,圖一會兒再上傳
hawking8987 回復(fù)于:2008-05-09 23:04:09
:mrgreen: :mrgreen: :mrgreen: :mrgreen: 好帖子 頂
simonzhan 回復(fù)于:2008-05-11 01:10:55
很好很強(qiáng)大啊,偶以前用的是cacti
skylove 回復(fù)于:2008-05-11 09:04:50
很不錯
sleepycat 回復(fù)于:2008-05-12 15:19:07
這么好的貼子,才看到,頂一下
netocool 回復(fù)于:2009-06-15 13:58:45
CentOS 5.2下的shell不能顯示中文,呵呵
platinum 回復(fù)于:2009-06-15 14:53:59
引用:原帖由 netocool 于 2009-6-15 13:58 發(fā)表 [url=http://linux./bbs/redirect.php?goto=findpost&pid=7042698&ptid=868463]
CentOS 5.2下的shell不能顯示中文,呵呵
不知你說的 tty 還是 pts?
如果是 pts,那應(yīng)該是你沒設(shè)置好
|