2020/08/19

如何在 Debian GNU/Linux 10 系統用服務顯示 Wifi 狀態與查詢傳輸量

Notify Wifi Status And Query Statistics Using Service On Debian 10


【摘要】
敘述在 Debian 10 系統用 systemd 的 Service 顯示 Wifi 狀態,
以及記錄與計算收發量的方法。


【目錄】


    【前言
    【抽取 syslog 的內容
    【監視記錄檔
    【連結的 .service 檔
    【處理 Wifi 狀態的指令
    【記錄 Wifi 傳輸量
    【查詢 Wifi 傳輸量
    【查詢 Wifi 傳輸量的累積圖
    【後語

【前言】

一、現在的智慧型手機大多有顯示或控制上網使用量的程式,因為不是每一個用戶都是申請上網吃到飽的付費方案。試用了手機本身的和幾個另外安裝的應用程式,發現較簡單的程式只顯示每個月的量,較詳細的可以顯示年、月、週、日的量。但似乎沒有一個可以查詢自訂時段的量,譬如某日上午八時到下午五點的傳輸量。所以就想出了本文的方法,特將結果記錄於此。


二、本例的手機沒有 rooted,無法存取手機的系統資料,所以沒有辦法在手機端動手腳。但因本例目前大多用電腦發送 Wifi 讓手機上網,所以就變通地在電腦端想辦法。雖然電腦 Wifi 的收發量都比手機的上網量要大一些,但因差距不大,做個參考,還是可以的。當手機使用 SIM 卡上網時,心裡才有個底,會用掉多少 $$。


三、本文的內容大致分為:

  1. 抽出 syslog 有關 Wifi 狀態的記錄
  2. 用 systemd 的服務(service)監視上述的記錄檔,當狀態改變時,顯示通知
  3. 在 Wifi 的啟用期間,定時記錄傳輸量;Wifi 中斷,即顯示通知並停止記錄
  4. 查詢某時段的 Wifi 收發量

四、本文新增的檔案較多,先將檔名和目的依序摘要於此,較易掌握整篇文章:

  1. net_status.conf:將含有 regex 的內容抽出到 .net_status_wl 檔
  2. notify-wl-status.path:監視 .net_status_wl 檔是否有修改;若有,呼叫 notify-wl-status.service
  3. notify-wl-status.service:執行 notify-wl-up 檔中的指令
  4. notify-wl-up:在 Wifi 開啟時,顯示狀態為 up,並執行 log-wl 的指令開始記錄傳輸量
  5. log-wl:記錄傳輸量;若 Wifi 中止,顯示狀態為 down,並停止記錄
  6. query-wl:查詢傳輸量
  7. query:查詢傳輸量的累積圖

  也因為檔案多,所以新增了一個「專用目錄」 /home/USER/.local/bin/notify_updown/,儘量將相關檔案都放在裡面,以方便檢視與管理。


五、本文將 wlpNsNbN 之類的無線網路界面名稱用界面名稱取代,各位要記得改成自己系統的名稱。執行 ip a 應該就可以查到了。此外,使用者名稱用USER代替,也要記得改成自己的登入名。


【抽取 syslog 的內容】

一、Debian GNU/Linux 10 系統是將網路狀態記錄在 /sys/class/net/界面名稱/operstate 檔案中,內容不是 up 就是 down。原本是直接用服務(service)監視此等檔案即可,但因是系統在控制 /sys/ 目錄中的內容,所以無法用 systemd 存取,權限不足。因此改用變通的辦法,監視可以存取又有記錄網路狀態的檔案。


二、Debian 系統的記錄檔大多是放在 /var/log/ 目錄,其中有記錄網路連線狀態的檔案至少有:kern.log、messages、syslog。

  剛開啟 Wifi 時,這三個檔案都會有以下記錄:

  1. IPv6: ADDRCONF(NETDEV_UP): 界面名稱: link is not ready
  2. IPv6: ADDRCONF(NETDEV_CHANGE): 界面名稱: link becomes ready

  中止 Wifi 時,會有以下記錄:

  1. IPv6: ADDRCONF(NETDEV_UP): 界面名稱: link is not ready

  所以,理論上監視三檔之一即可。但問題是這三個檔案也會記錄其他事件,直接監視它們會有一大堆的回應,所以要把上述 Wifi 狀態的記錄抽出並存到另一檔,才來監視。


三、Debian 系統的 syslog 設定檔是 /etc/rsyslog.conf。其中有一行是:

  1. $IncludeConfig /etc/rsyslog.d/*.conf

  這是要納入 /etc/rsyslog.d/ 目錄中所有副檔名是 .conf 的設定,因此可以在此目錄新增自己的設定。本例用 net_status.conf,內容為:

  1. :msg, regex, " IPv6: .* 界面名稱: link " /tmp/.net_status_wl

  說明:

  • 冒號:用 property 篩選訊息,這個冒號一定要在行首。
  • msg:訊息中的 MSG 部分。
  • regex:用 regular expression 來篩選訊息。
  • "...":regex 的內容。這裡其實只要有「 IPv6: 」就可以了;但為了更具獨特性,所以加了界面名稱。各位要記得改成自己系統的值,不然就用「 IPv6: 」也可以。
  • 最後一項:篩選出的訊息所要儲存的路徑,自訂。記錄檔放在 /tmp/ 目錄中是因為關機時會刪掉,再要記錄時才建立新檔,也就是有自動清除的功能。
  • 詳細說明請見 man 5 rsyslog.conf

  註:本例是將 net_status.conf 放在自家專用目錄中,再新增連結到 /etc/rsyslog.d/ 目錄。這樣要修改比較方便,不需有 root 權限。


四、新增 net_status.conf 檔後,要使之生效請執行:

  1. sudo /etc/init.d/rsyslog restart

  或

  1. sudo systemctl restart rsyslog.service

  等 Wifi 狀態有所改變時,執行 sudo cat /tmp/.net_status_wl 看看有沒有新增訊息。本網誌曾發表過電腦和手機互用網路的方法,其中有開關 Wifi 的步驟,願意的人可以參考。


【監視記錄檔】

一、systemd 的服務有很多種檔案類型(Type),譬如:.service、.socket、.path、.target、.mount、.automount、.timer、.device、.scope、.swap、.slice 等。每一個都是一個 unit。因為本文的一個目的是要即時通知 Wifi 狀態的改變,所以必需持續監視,選擇的服務類型就一定是 .path。

  Path 類型是指定一或多個路徑,讓 systemd 監視。當任一路徑有「指定的變化」時,啟動其指定的服務檔中所設定的程式。


二、所謂「指定的變化」可以用的選項有:

  • PathExists=  指定的檔案或目錄是否存在
  • PathExistsGlob=  指定的檔案或目錄是否存在,設定值可以用 regular expression 格式
  • PathChanged=  指定的檔案或目錄是否改變,這是看該路徑是否被關閉
  • PathModified=  指定的檔案或目錄是否被修改,這是看該路徑是否有寫入的動作
  • DirectoryNotEmpty=  指定的目錄是否至少含有一個檔案或目錄

  本例選用 PathModified=,而所用的檔名是 notify-wl-status.path。


三、notify-wl-status.path 的內容是:

  1. [Unit]
  2. Description=Notify Wifi Status

  3. [Path]
  4. PathModified=/tmp/.net_status_wl
  5. Unit=notify-wl-status.service

  6. [Install]
  7. WantedBy=multi-user.target

  說明:

  • 檔案要放在 /etc/systemd/system/ 目錄中;也可以放在其他目錄,再新增連結到前述目錄。
  • [Unit] 一節是設定該檔的一般資訊。
  • Description 是該 unit 的名稱,自訂。
  • [Path] 一節設定要監視的路徑(此處用 PathModified=,說明見上)和要連結的服務(用 Unit= 選項設定)。
  • Unit= 設定值不能是 .path 的 unit;官方文件建議用相同的檔名,只有副檔名不同,本例即依此原則命名。
  • [Install] 一節是設定安裝該檔的資訊。
  • WantedBy= 是列舉對應的 units。也就是使用 systemctl enable 本檔時,要建立連結的地方。
  • .path 檔不能有 [Service] 一節,若有,會被忽略。
  • 詳細說明請見 man 5 systemd.path(談 [Path])和 man 5 systemd.unit(談 [Unit][Install])。

  註:本例是將此檔放在自家專用目錄中,再新增連結到 /etc/systemd/system/ 目錄。這樣要修改比較方便,不需有 root 權限。


四、檢查語法是否正確:

  1. systemd-analyze verify /etc/systemd/system/notify-wl-status.path

【連結的 .service 檔】

一、此類 .service 檔設定由 systemd 控管的程序(process)的資訊。notify-wl-status.service 檔的內容很簡單,只有必要的項目:

  1. [Unit]
  2. Description=Notify Wifi Status

  3. [Service]
  4. ExecStart=/usr/bin/bash /home/USER/.local/bin/notify_updown/notify-wl-up

  說明:

  • 檔名雖然可以自己決定,但最好和 .path 檔一樣,只差在副檔名用 .service。
  • 檔案放在 /etc/systemd/system/ 目錄中;也可以放在其他目錄,再新增連結到前述目錄。
  • [Unit] 一節是設定該檔的一般資訊。
  • Description 是該 unit 的名稱,自訂。
  • [Service] 一節設定該服務特有的設定。
  • ExecStart= 設定啟動該服務時要執行的指令,可含引數,也可以是一指令檔。若是檔案,可以放在任何目錄,但需用全路徑。
  • 詳細說明請見 man 5 systemd.service(談 [Service])。

  註:本例是將本檔放在專用目錄中,再新增連結到 /etc/systemd/system/ 目錄。這樣要修改比較方便,不需有 root 權限。


二、檢查語法是否正確:

  1. systemd-analyze verify /etc/systemd/system/notify-wl-status.service

三、將上述 .path 和 .service 檔放到 /etc/systemd/system/ 目錄或修改內容後,用此指令重新載入:

  1. sudo systemctl daemon-reload

  若沒有任何訊息,表示正確,便可啟動:

  1. sudo systemctl start notify-wl-status.path

  notify-wl-status.service 檔不需啟動,它是被 .path 檔呼叫的。

  看 .path 的狀態用:

  1. systemctl status notify-wl-status.path

  若顯示綠字的 active (waiting),表示應該沒有問題,便可用下面指令安裝:

  1. sudo systemctl enable notify-wl-status.path

  這會在 /etc/systemd/system/multi-user.target.wants/ 目錄新增 notify-wl-status.path 連結檔,連結的目標即是 /etc/systemd/system/notify-wl-status.path。如此一來,系統重新開機時,即會啟動 notify-wl-status.path,繼續監視 /tmp/.net_status_wl。


【處理 Wifi 狀態的指令】

上面設定啟動的程式是一指令檔 notify-wl-up,其內容如下:

#!/usr/bin/bash
 
if [ "$(cat /sys/class/net/界面名稱/operstate)" == "up" ]
#目前的 Wifi 狀態為 up 才執行
then
  env DISPLAY=:0 env XAUTHORITY=/home/USER/.Xauthority zenity --notification --text="  Wifi up" --window-icon=/home/USER/.local/bin/notify_updown/wifi-up.png --timeout=2
  #顯示狀態
  bash /home/USER/.local/bin/notify_updown/log-wl
  #開始記錄傳輸量
fi
 
exit 0

說明:

  • 一開始即篩選出 Wifi 為 up 的狀況,先顯示狀態,然後用 log-wl 記錄傳輸量。
  • 此檔只處理 Wifi up 的狀況,而 Wifi down 是在下一檔 log-wl 才處理。
 

【記錄 Wifi 傳輸量】

log-wl 檔的指令是每隔一段自訂時間就讀取系統的 Wifi 傳輸值並記錄到指定的檔案。

系統的 Wifi 傳輸值是在 /sys/class/net/界面名稱/statistics/ 目錄中;rx_bytes 是目前接收量,tx_bytes 是目前傳送量,單位都是位元組(bytes)。這兩個檔案都各只有一個最新的值,而且系統關機就刪除,所以必需把它們記錄到其他的檔案,才能在日後查詢。

log-wl 檔內容如下:

#!/usr/bin/bash
#記錄 Wifi 的傳輸量
 
Interval=60
#記錄的間隔,單位:秒;最小值是 60 秒,需為 60 的倍數
 
#輸出檔若不存在,新增之;更改權限
if [ ! -e /tmp/.wl-rx_bytes ]; then echo "1970-1-1 0:0 0 0" > /tmp/.wl-rx_bytes; fi
if [ ! -e /tmp/.wl-tx_bytes ]; then echo "1970-1-1 0:0 0 0" > /tmp/.wl-tx_bytes; fi
sudo chown USER /tmp/.wl-rx_bytes /tmp/.wl-tx_bytes
sudo chmod g+w /tmp/.wl-rx_bytes /tmp/.wl-tx_bytes
 
while true
#持續記錄直到 Wifi 中止
do
  Wait=$(( ${Interval} - $(date +%S) ))
  #到指定的間隔尙需等待的時間
 
  for (( i=${Wait}; i>0; i-- )) {
  #等待期間每秒檢查一次 Wifi 是否停止
    sleep 1
    if [ "$(cat /sys/class/net/界面名稱/operstate)" == "down" ]
    #若 Wifi 停止,即顯示狀態並中止程式
    then
      env DISPLAY=:0 env XAUTHORITY=/home/USER/.Xauthority zenity --notification --text="  Wifi down" --window-icon=/home/USER/.local/bin/notify_updown/wifi-down.png --timeout=2
      exit
    fi
  }
 
  Time=$(date +"%Y-%m-%d %H:%M ")$(( $(date +%s) / 10 * 10 ))
  #目前的時間,格式:年-月-日 時:分 epoch秒
  #%s是「epoch秒」,即某時刻到 1970-01-01 00:00:00 UTC 的秒數
  #調整記錄的時間到整分,即秒數為 0,差距最多只有 1 秒
 
  RX=$(cat /sys/class/net/界面名稱/statistics/rx_bytes)
  #系統記錄的目前接收量,單位:bytes(位元組)
  if [ ${RX} -ne $(tail -n1 /tmp/.wl-rx_bytes | awk '{ print $4 }') ]
  #只有當目前值和前一次的記錄值不同才記錄
  then
    echo "${Time} ${RX}" >> /tmp/.wl-rx_bytes
    #記錄內容是:年-月-日 時:分 epoch秒 接收量
  fi
 
  TX=$(cat /sys/class/net/界面名稱/statistics/tx_bytes)
  #系統記錄的目前傳送量,單位:bytes(位元組)
  if [ ${TX} -ne $(tail -n1 /tmp/.wl-tx_bytes | awk '{ print $4 }') ]
  #只有當目前值和前一次的記錄值不同才記錄
  then
    echo "${Time} ${TX}" >> /tmp/.wl-tx_bytes
    #記錄內容是:年-月-日 時:分 epoch秒 傳送量
  fi
done
 
exit 0

說明:

  • 記錄檔的格式是:年-月-日 時:分 epoch秒 傳輸量
  • 記錄的內容有「年-月-日 時:分」,這是給人看的;「epoch秒」是查詢傳輸量時計算間隔用的;二者是相同的時間。
  • 間隔請修改成自己的偏好值,每十分鐘、每小時都可以,單位是秒;最小值是 60 秒,需為 60 的倍數。
  • 輸出檔若不存在或內容格式不對,後續的指令會有問題,所以不但要先建立,還要加入符合格式的內容,此處用 epoch=0 的時間。
  • 調整記錄的時間到整分是為了查詢方便,因為查詢只輸入到分鐘,不含秒。
  • 在等待到整分的期間也有可能中止 Wifi,所以必需經常檢查,才能立刻通知並中斷記錄,此處每秒檢查一次。
  • 只有當值不同時才記錄是為了節省空間,不然待機時也一直記錄相同的值,記錄檔會很大。
  • 記錄檔放在 /tmp/ 目錄中是因為關機時會刪掉,再要記錄時才建立新檔,也就是有自動清除的功能。如果不清除的話,在查詢傳輸量時,會有前值比後值還要大的問題,讓傳輸量變成負的。

【查詢 Wifi 傳輸量】

一、說明:

  • 本例用 zenity 的對話框詢問要查詢的起訖時間。但為了讓查詢者知道可以查詢的時間範圍,要先找出容許的起點和終點。若查詢的時間超出了容許範圍,就中止程式。
  • 由於記錄的資料是表格的型式,而且計算與結果的顯示會有小數,所以用 awk 較適合。
  • 系統記錄的傳輸量是用位元組(B)做單位,以下的指令會在適當的時機將單位改成較易閱讀的 KB 或 MB。
  • 其他的說明請見檔內註解。
  • 將下述的指令存成檔案後,改成可執行檔,直接執行此檔會比較方便。

二、query-wl 檔的內容如下:

#!/usr/bin/bash
#計算 Wifi 的傳輸量
 
FileRx=/tmp/.wl-rx_bytes
FileTx=/tmp/.wl-tx_bytes
#記錄傳輸量的路徑;由 log-wl 檔設定
 
#若記錄檔第一行是自增的 1970-1-1...,即刪除之
if [ "$(head -n1 ${FileRx})" == "1970-1-1 0:0 0 0" ]
then
  sed '1d' ${FileRx} > temp && mv temp ${FileRx}
fi
if [ "$(head -n1 ${FileTx})" == "1970-1-1 0:0 0 0" ]
then
  sed '1d' ${FileTx} > temp && mv temp ${FileTx}
fi
 
MinEpochRx=$(head -n1 ${FileRx} | awk '{ print $3 }')
MinEpochTx=$(head -n1 ${FileTx} | awk '{ print $3 }')
#上面兩檔的第一行記錄著開始記錄的時間,將之取出,才能知道容許查詢的範圍
#因為有進出兩個值,為了查詢時不要去查比最小值還要小的,所以選二者中較晚的時間做極限
if [ ${MinEpochRx} -lt ${MinEpochTx} ]
then
  MinEpoch=${MinEpochTx}
else
  MinEpoch=${MinEpochRx}
fi
MinTime=$(date --date=@${MinEpoch} +"%Y-%m-%d %H:%M")
#改為人能看得懂的格式
 
MaxEpochRx=$(tail -n1 ${FileRx} | awk '{ print $3 }')
MaxEpochTx=$(tail -n1 ${FileTx} | awk '{ print $3 }')
#上面兩檔的最後一行記錄著最晚的記錄時間,將之取出,才能知道容許查詢的範圍
#因為有進出兩個值,為了查詢時不要去查比最大值還要大的,所以選二者中較早的時間做極限
if [ ${MaxEpochRx} -lt ${MaxEpochTx} ]
then
  MaxEpoch=${MaxEpochRx}
else
  MaxEpoch=${MaxEpochTx}
fi
MaxTime=$(date --date=@${MaxEpoch} +"%Y-%m-%d %H:%M")
#改為人能看得懂的格式
 
TimeBegin=$(zenity --forms --title="請輸入計量開始時間" --text=" 計量開始時間,有效範圍:${MinTime} ~ ${MaxTime} " --add-calendar="年、月、日" --add-entry="時:分 (24小時制,如 2:5)" --forms-date-format="%Y-%m-%d" --separator=" ")
#詢問查詢的起點;因為要將輸入的值存起來,所以整個 zenity 指令用小括號包起來
#在說明文字中顯示有效的起訖時間
 
if [ $? -ne 0 ]; then exit; fi
#若選 [Cancel],即中止程式
 
TimeBegin=$(echo ${TimeBegin} | sed "s/:/ /" | awk '{ printf "%s %02d:%02d", $1, $2, $3 }')
#將輸入值改成標準格式
EpochBegin=$(date --date="${TimeBegin}" +%s)
#轉換成 epoch秒,才好計算間隔
 
TimeEnd=$(zenity --forms --title="請輸入計量結束時間" --text=" 計量結束時間,有效範圍:${MinTime} ~ ${MaxTime} " --add-calendar="年、月、日" --add-entry="時:分 (24小時制,如 2:5)" --forms-date-format="%Y-%m-%d" --separator=" ")
 
if [ $? -ne 0 ]; then exit; fi
#若選 [Cancel],即中止程式
 
TimeEnd=$(echo ${TimeEnd} | sed "s/:/ /" | awk '{ printf "%s %02d:%02d", $1, $2, $3 }')
EpochEnd=$(date --date="${TimeEnd}" +%s)
#詢問查詢的終點,並轉換成 epoch秒
 
#檢查輸入的範圍是否在記錄的範圍內,若超出,計算會有問題,所以立刻中止程式
if [ ${EpochEnd} -lt ${MinEpoch} -o ${EpochEnd} -gt ${MaxEpoch} ]
then
  echo "輸入值超出範圍"
  exit
fi
 
#計算並顯示傳輸量
#因為要用 zenity 顯示結果,所以把整個 awk 指令的輸出設成 zenity 要顯示的內容
zenity --info --no-wrap --text=\
"$(awk -v epoBegin="${EpochBegin}" -v epoEnd="${EpochEnd}" 'BEGIN { Flag=0; Unit="KB" } {
#Flag 是用來標識是否在查詢範圍內
  if ((Flag == 0) && ($3 >= epoBegin)) {
  #起點
    EBegin=EEnd=$3
    #範圍時間的起點
    bytesBegin=bytesEnd=$4
    #範圍內傳輸量的起始值
    Flag=1
    #進入查詢範圍,便將標記打開
    sub("^.*/", "" ,FILENAME)
    #輸入檔的全路徑只取檔名
    next
    #略過以下指令,直接讀入並處理下一筆資料
  }
  if ((Flag == 1) && ($3 <= epoEnd)) {
  #範圍內持續更新終點值
    EEnd=$3
    bytesEnd=$4
    if ($3 != epoEnd) { next }
    #若剛好是查詢的終點,可能已達檔末,再呼叫下一行,會有錯,所以先剔除這種狀況
  } 
  if ((Flag == 1) && ($3 >= epoEnd)) {
  #終點
    Kbytes=(bytesEnd - bytesBegin) / 1024
    #計算範圍內的傳輸量並將單位由 B 改成 KB
    switch (FILENAME) {
    #將不同檔案的值存到不同的變數
      case ".wl-rx_bytes":
      #接收
        rx=Kbytes
        break
      case ".wl-tx_bytes":
      #傳送
        tx=Kbytes
        break
      default:
        break
    }
    Flag=0
    #在處理下一檔前要將標記改成非在範圍內
    nextfile
  }
} END {
#前面是取得所需的數據,END 段輸出結果
    if (rx+tx > 2000) { rx /= 1024; tx /= 1024; Unit="MB" }
    #若用 MB 做單位較適合的話,就改成較易看懂的單位
    printf "接收量/傳送量/總和: %.2f / %.2f / %.2f %s    \n\n", rx, tx, rx+tx, Unit
    printf "間隔: %d 分鐘\n\n", (EEnd-EBegin)/60
    printf "速率: %.2f / %.2f / %.2f %s/min\n", 60*rx/(EEnd-EBegin), 60*tx/(EEnd-EBegin), 60*(rx+tx)/(EEnd-EBegin), Unit
    #傳輸率是傳輸量除以真實間隔
    #真實間隔不一定是查詢的範圍
}' ${FileRx} ${FileTx})"
#接收和傳送都用相同指令處理,所以接連地讀入
 
exit 0

【查詢 Wifi 傳輸量的累積圖】

一、說明:

  • 前法是計算所選範圍的總值,此法是顯示每隔一指定間隔的傳輸量與其累積圖,優點是看得到隨時間的變化。
  • 此程式碼可查詢有線或無線、接收或傳送的結果,但需先依前面所述,完成傳輸量的記錄才行。有線網路只要修改界面名稱等相關資訊即可,因內容大同小異,故不再贅述。
  • 使用方法:開啟虛擬終端機,將視窗最大化,執行:
  1. bash query {en|wl} {rx|tx} interval
    en:ethernet/有線, wl:wireless/Wifi,二選一,在前
    rx:接收, tx:傳送,二選一,在後
    interval: 間隔秒數,正整數;建議至少 60,如:600(十分鐘)、1800(半小時)、3600(一小時)
  • 輸出的內容是每隔一指定的時間,顯示日期、時間、傳輸量(以 MB 為單位)、累積圖。
  • 其他的說明請見檔內註解。

二、query 檔內容如下:

#!/usr/bin/bash
#查詢網路的傳輸量;有線或無線,接收或傳送皆可
 
#檢查語法是否正確
case ${1} in
"en" | "wl" )
  ;;
* )
  echo "  使用方法:bash query {en|wl} {rx|tx} interval"
  echo "  en:ethernet/有線, wl:wireless/Wifi,二選一,在前"
  echo "  rx:接收, tx:傳送,二選一,在後"
  echo "  interval: 間隔秒數,正整數;建議至少 60"
  exit 1;;
esac
 
#檢查語法是否正確
case ${2} in
"rx" | "tx" )
  ;;
* )
  echo "  使用方法:bash query {en|wl} {rx|tx} interval"
  echo "  en:ethernet/有線, wl:wireless/Wifi,二選一,在前"
  echo "  rx:接收, tx:傳送,二選一,在後"
  echo "  interval: 間隔秒數,正整數;建議至少 60"
  exit 1;;
esac
 
#檢查間隔是否為正整數,至少十秒
if [ ${3} -lt 10 ]
#若非整數,會有錯誤訊息
then
  echo "間隔秒數有問題"
  exit 1
fi
 
File=/tmp/.${1}-${2}_bytes
#記錄傳輸量的路徑;由 log-en/log-wl 檔設定
 
Interval=${3}
#計算的間隔;單位:秒
 
if [ "$(head -n1 ${File})" == "1970-1-1 0:0 0 0" ]
#若記錄檔第一行是自增的 1970-1-1...,即刪除之
then
  sed '1d' ${File} > temp && mv temp ${File}
fi
 
MinTime=$(head -n1 ${File} | awk '{ print $1, $2 }')
MinEpoch=$(head -n1 ${File} | awk '{ print $3 }')
#上檔的第一行記錄著開始記錄的時間,將之取出,才能知道容許查詢的範圍
 
MaxTime=$(tail -n1 ${File} | awk '{ print $1, $2 }')
MaxEpoch=$(tail -n1 ${File} | awk '{ print $3 }')
#上檔的最後一行記錄著最晚的記錄時間,將之取出,才能知道容許查詢的範圍
 
TimeBegin=$(zenity --forms --title="請輸入計量開始時間" --text=" 計量開始時間,有效範圍:${MinTime} ~ ${MaxTime} " --add-calendar="       年、月、日" --add-entry="時:分 (24小時制,如 2:5)" --forms-date-format="%Y-%m-%d" --separator=" ")
#詢問查詢的起點;因為要將輸入的值存起來,所以整個 zenity 指令用小括號包起來
#在說明文字中顯示有效的起訖時間
 
if [ $? -ne 0 ]; then exit 2; fi
#若選 [Cancel],即中止程式
 
TimeBegin=$(echo ${TimeBegin} | sed "s/:/ /" | awk '{ printf "%s %02d:%02d", $1, $2, $3 }')
#將輸入值改成標準格式
EpochBegin=$(date --date="${TimeBegin}" +%s)
#轉換成 epoch秒,才好計算間隔
 
TimeEnd=$(zenity --forms --title="請輸入計量結束時間" --text=" 計量結束時間,有效範圍:${MinTime} ~ ${MaxTime} " --add-calendar="       年、月、日" --add-entry="時:分 (24小時制,如 2:5)" --forms-date-format="%Y-%m-%d" --separator=" ")
 
if [ $? -ne 0 ]; then exit 2; fi
#若選 [Cancel],即中止程式
 
TimeEnd=$(echo ${TimeEnd} | sed "s/:/ /" | awk '{ printf "%s %02d:%02d", $1, $2, $3 }')
EpochEnd=$(date --date="${TimeEnd}" +%s)
#詢問查詢的終點,並轉換成 epoch秒
 
#檢查輸入的範圍是否在記錄的範圍內,若超出,計算會有問題,所以立刻中止程式
if [ ${EpochEnd} -lt ${MinEpoch} -o ${EpochEnd} -gt ${MaxEpoch} ]
then
  echo "輸入值超出範圍"
  exit 1
fi
 
BytesEnd=$(cat ${File} | grep ${EpochEnd} | awk '{ print $4 }')
#查詢範圍之最大傳輸量;用來調整累積圖不要超過一行,暫定 100 個 marks
 
#計算並顯示傳輸量
awk -v epoBegin=${EpochBegin} -v epoEnd=${EpochEnd} -v interv=${Interval} -v BEnd=${BytesEnd} 'BEGIN {
  Flag=0
  #標識是否在查詢範圍內;0 否;1 是
  Counter=1
  #用來計算間隔數
  Factor=BEnd/1024/1024/100
  #調整累積圖不要超過 100 個 marks,以免跨行;虛擬終端機視窗要最大化
  printf(" 年-月-日  時:分 傳輸MB 累積圖\n")
  #欄名
} {
  Mbytes=$4/1024/1024
  #該行所列之傳輸量換算為 MB
  if ((Flag == 0) && ($3 >= epoBegin)) {
  #起點
    EBegin=$3
    printf("%s %s %5d  %s\n", $1, $2, Mbytes, mark(Mbytes, Factor))
    Flag=1
    #進入查詢範圍,便將標記打開
    next
    #略過以下指令,直接讀入並處理下一筆資料
  }
  if ((Flag == 1) && ($3 < epoEnd)) {
  #範圍內持續輸出
    Value=EBegin+Counter*interv
    #該間隔的時間
    if ($3 >= Value) {
    #達到間隔的時間才輸出
      printf("%s %s %5d  %s\n", $1, $2, Mbytes, mark(Mbytes, Factor))
      Counter+=(1+int(($3-Value)/interv))
      #下一個間隔;若中間缺數據,要補上所缺的間隔數
    } else {
      if ($3 != epoEnd) { next }
      #未達間隔時間,便處理下一行
      #但若剛好是查詢的終點,可能已達檔末,再呼叫下一行,會有錯,所以先排除這種狀況
    }
  } 
  if ((Flag == 1) && ($3 >= epoEnd)) {
  #終點
    printf("%s %s %5d  %s\n", $1, $2, Mbytes, mark(Mbytes, Factor))
    exit 0
  }
}
#製作累積圖的一條,用星號做 marks
function mark(mbytes, factor,   str, i) {
  if (factor < 1) { factor=1 }
  #只縮小,不放大
  for (i=1; i<=mbytes/factor; i++) {
    str=str "*"
  }
  return str
}' ${File}
#return value: 0 normal, 1 error, 2 quit

【後語】

一、如果開關 Wifi 並沒有看到通知,應該是因為未經由 .path unit 的關係。


二、若有問題,用 sudo cat /var/log/syslogsudo journalctl -xe 看看是哪裡錯了。


三、本例是在自己組合的 Debian GNU/Linux 10 系統撰寫與測試的,不同的系統,目錄、檔名可能會不一樣,請自行修改。


沒有留言:

張貼留言