主頁 > 作業系統 > 使用 shell 腳本自動獲取發版指標資料

使用 shell 腳本自動獲取發版指標資料

2022-04-21 06:13:45 作業系統

問題背景

大一點的公司都會建立一套規章流程來避免低級錯誤,例如合入代碼前必需經過同行評審;上線前必需提測且通過 QA 驗證;全量前必需經過 1%、5%、10%、20%、50% 的灰度程序,尤其是最后一步,需要嚴密的監控發版指標來保證新版本的質量,如果與主力版本的指標相比有例外變動,就需要及時停止放量并分析原因,

一個版本的重點觀察指標,除崩潰率外有小 20 項,分布在系統的 10 多個頁面,且每個指標均需要指定多達 6-10 個過濾條件,最常用的包括版本號、端型別 (PC/ Mac/Android/iOS/…)、用戶型別 (user/vip/svip),此外還有一些復雜的下拉串列選項,每次都記不住,需要參考檔案才能確定選對了 ??;另外像版本號這種選項,系統需要很長時間才能刷出來全部版本串列,有時等了很長時間也出不來,還得手動刷一下才能好;最后,有一些指標系統里沒有直接給出,需要綜合多個指標資料進行計算,例如版本流量占比是由版本流量除以總流量得出的,類似的還有播放流量占比;另外還有一些通用的計算,例如速度的單位是 B/s,實際上使用 MB/s 更貼切,人工記錄資料時,一般直接除以 1000 來進行簡單估算,與除以 1024 相比還是有比較大誤判的,走一遍完整流程下來,快了也得半小時,慢了一上午就過去了,

解決方案

凡是重復性的勞動都有優化空間,凡是收集資料的作業都能用腳本完成——本著這兩個原則,嘗試做一個自動獲取發版指標資料的 shell 腳本,之前有使用 curl 訪問 restful api 的經驗 (用 shell 腳本做 restful api 介面監控),這次訪問 web 服務器原理也是一樣的,通過瀏覽器的頁面除錯功能,可以查看到一次請求的詳細資訊:

主要使用的是 http post 資料,資料基于 json 格式回傳:

不同請求回傳的 json 格式不同,不過都可以使用 jq 命令處理,

拉取資料

用 curl 嘗試一下:

curl -s "http://iyuntu.xxxxx.com/xxxxxx/api/xxxxxxxxxxxxx/" -H "Accept: */*" -H "Connection: keep-alive" 
-H "Content-Type: application/x-www-form-urlencoded; charset=UTF-8" -H "Accept-Encoding: gzip, deflate"  
-d "start=1642137792&end=1642310592&method=p2pflow&version=3.0.0.112&vipLevel=all&clusterItem=cluster_hour&clientType=pc"

提交的表單資料與 web 請求完全一致,然而得到了服務器錯誤:

{"error_code":1006,"message":"userinfo is wrong.","data":[]}

提示用戶資訊錯誤,難道是因為沒有攜帶登錄資訊?再看一下瀏覽器中請求的 cookie 資訊:

確實不少,將整個 cookie 攜帶到 curl 的請求中:

curl -s "http://iyuntu.baidu.com/clientive" -H "Content-Type: application/x-www-form-urlencoded; charset=UTF-8" -H "Accept-Encoding: gzip, deflate"  
-d "start=1642137792&end=1642310592&method=p2pflow&version=3.0.0.112&vipLevel=all&clusterItem=cluster_hour&clientType=pc" 
--cookie 'XXXXXXX=6955BFF6EBA75EA12FB35312F4B67309:FG=1; UUAP_TRACE_TOKEN=00253a0352ac05ddf2abb3867e1383dc; Hm_lvt_8d2a248ae863804cbd8d4f34ef769db3=1641283185,1641283813,1641780693,1642304682; jsdk-uuid=0c8f1b25-11a8-4b29-9bde-8203c9d92ba6; RT="z=1&dm=baidu.com&si=h7sa38uz0c7&ss=kycewdz5&sl=0&tt=0&bcn=https%3A%2F%2Ffclog.xxxxx.com%2Flog%2Fweirwood%3Ftype%3Dperf&ld=itd&cl=hr8&ul=kzlat&hd=kzlf2"; XXX_X_XXXXX=BppxvwS4efrHrfU1N5YBV52pvnablZcVWysHnik+JuWM8I/Ujn+rS8e2vD2ig3MkYKVYXq326XyE8GeQThgT7g==; XXXXX=GkyaW9RbklUY0VLRWpac3hMUlRsNjh5M25PTlNiZWxGRVdvT1pnWDE3ZXVuUHRoRVFBQUFBJCQAAAAAAAAAAAEAAABjkC9mY2F2ZXBhcGVybWFuAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK4P1GGuD9RhS; UUAP_P_TOKEN=PT-685223097977524224-iAPxtFmvd3-uuap; jsdk-user=d6zmte6yU7ahGWlxTQZghw==; PHPSESSID=ST-689506677214060545-7KJVo-uuap; Hm_lpvt_8d2a248ae863804cbd8d4f34ef769db3=1642320803'

這次不報錯了,但是也沒有請求到任何結果,查看 curl 回傳值:

$ echo $?
23

百度了一下,curl 23 錯誤是寫失敗,難道需要重定向到檔案?為上面的命令加入:

--output temp.dat

將結果保存在 temp.dat 檔案中,這次 curl 正常了,但查看 temp.dat 卻是一團亂麻:

$ head -n 1 temp.dat
???E?E?7?!?b
            A,9p?6
                  ???YuN??n ?c?'??n?5????[?????c/?>??_???????????????z_?_??m>~?R????gI?=_????G_????X???????9k?5????

難道是被壓縮了?使用 gunzip 解壓試試:

$ cat temp.dat  | gunzip
{"p2p\u6d41\u91cf":[[1642140000000,28249601382.447],[1642143600000,29701279461.349],[1642147200000,30004732054.571],
[1642150800000,28226621579.753],[1642154400000,27565004131.18],[1642158000000,30050204384.371],[1642161600000,34357590257.653],
[1642165200000,37445146977.384],[1642168800000,37507405282.629],……

確實是,解壓后得到的就是 json 內容了,內容決議暫時放一下,先聚焦一下 cookie ,

使用瀏覽器 cookie 可以得到想要的結果,但會對瀏覽器形成依賴——每次跑腳本前需要從瀏覽器抓一份 cookie 保存在本地,經過一番探究,發現只要保留 cookie 中的這一條就能訪問:

PHPSESSID=ST-689506677214060545-7KJVo-uuap;

應該是 SSO 登錄后的訪問憑證,從瀏覽器復制一條 cookie 雖然有一點麻煩,但也不是不能接受,相比手工記錄發版指標資料,還是友好不少了嘛~

下面以流量指標為例,串起來上面的一系列命令:


# @param: starttime
# @param: endtime
# @param: version
# @param: clienttype
# @param: cookie
# @param: select-time [option]
function fetch_flow()
{
    local starttime="$1"
    local endtime="$2"
    local version="$3"
    local clienttype="$4"
    local cookie="$5"
    local selecttime=""
    if [ $# -gt 5 ]; then 
        selecttime="$6"
    fi

    local data="https://www.cnblogs.com/goodcitizen/archive/2022/04/20/start=${starttime}&end=${endtime}&method=p2pflow&version=${version}&vipLevel=all&clusterItem=cluster_hour&clientType=${clienttype}"
    curl -s "http://${HOST}/client/api/xxxxxxxxxxxx/" -H "Accept: */*" -H "Connection: keep-alive" -H "Content-Type: application/x-www-form-urlencoded; charset=UTF-8" -H "Accept-Encoding: gzip, deflate" -H "Origin: ${HOST}" -H "Referer: http://${HOST}/stability/main?typeName2=total_flow&typeName2=cluster_hour&typeName2=${clienttype}&datepicker0=${starttime}&datepicker1=${endtime}" -H "X-Requested-With: XMLHttpRequest" --cookie "$cookie"  -d "$data" --output temp.gzip

    if [ $? -eq 0 ]; then 
        echo "request flow ok"
        cat temp.gzip | gunzip > temp.txt
        
        # handle data here...
    fi
}

做個簡單說明:

  • 一些引數是從外部傳入的,詳見引數命名
  • 提前拼接好表單資料備用
  • curl 發送請求,多了一些 http 頭,主要是參考 web 請求設定的,實測可有可無
  • 請求中指定的 cookie 是從外部傳入的,這個引數其實就是從瀏覽器存盤到檔案后傳遞進來的
  • curl 回應存放在 temp.gzip 檔案中,使用 gunzip 解壓縮到 temp.txt 檔案,后面就可以用 txt 進行資料決議了

實測 cookie 中的 PHPSESSID 失效時間非常短,可能也就半個小時,基本每次執行腳本時都需要重新抓取,

決議資料

有了 json 資料,剩下的就是從中取得關心的部分,從上一節的示例可以看出,Web 介面回傳的資料都是按時間順序排列的,而發版資料只記錄某一個斷面的指標 (精確到小時) ,一般是選取流量高峰時刻,結合以上兩個需求,首先需要按時間的順序列出總流量的串列,用戶根據這個資訊選取流量高峰,或者選擇某個時刻;然后根據選取的時刻,所有指標資料向這個時刻看齊,保證資料的一致性,

例如對于上一節獲取到的資料,如何選取總流量呢:

{
    "p2p\u6d41\u91cf":Array[48],
    "dcdn\u6d41\u91cf":Array[48],
    "third_http\u6d41\u91cf":Array[48],
    "onecloud\u6d41\u91cf":Array[48],
    "relay\u6d41\u91cf":Array[48],
    "\u603b\u6d41\u91cf":Array[48],
    "cdn\u603b\u6d41\u91cf":Array[48],
    "bt\u603b\u6d41\u91cf":Array[48],
    "onecloud-http\u603b\u6d41\u91cf":[
        [
            1642140000000,
            84124693465.349
        ],
        [
            1642143600000,
            90980062017.307
        ],
        [
            1642147200000,
            86051227112.929
        ],
        ...
    ]
}

整個 json 的結構是這樣的:

  • key-value 構成的二值陣列 (更像 pair) 是最基本的單位,代表一個時刻的流量值
  • pair 組成的陣列構成一個維度,代表某一分量隨時間變化的曲線,每條曲線的維度由名稱確定
  • 多個維度組合成一個最終的 json object

首先要確認獲取哪個維度,json 中的漢字會被轉碼為 utf8,"\u603b\u6d41\u91cf" 代表的就是"總流量"了,在 jq 中可以直接指定漢字:

$ cat temp.txt | jq '."總流量"'
[
  [
    1642140000000,
    1126527342256.8
  ],
  [
    1642143600000,
    1176541641172.7
  ],
  [
    1642147200000,
    1177760044237.3
  ],
  ...
]

 注意這里漢字必需用引號包裹,否則會報錯:

jq: error: syntax error, unexpected INVALID_CHARACTER (Unix shell quoting issues?) at <top-level>, line 1:
.總流量 
jq: error: try .["field"] instead of .field for unusually named fields at <top-level>, line 1:
.總流量
jq: 2 compile errors

接著通過陣列符將資料一維化 (去掉最外層陣列),方便后續處理:

$ cat temp.txt | jq '."總流量"[]'
[
  1642140000000,
  1126527342256.8
]
[
  1642143600000,
  1176541641172.7
]
[
  1642147200000,
  1177760044237.3
]
...

將 key-value 的二值陣列也去掉,這個費了很大周折,不過總算找到了的辦法:

$ cat temp.txt | jq '."總流量"[]|.[0],.[1]'
1642140000000
1126527342256.8
1642143600000
1176541641172.7
1642147200000
1177760044237.3
...

使用了 jq 的內置管道,在陣列中挑選要提取的元素下標,關于 jq 語法可參考文末鏈接,

簡化為這樣的形式,再展示給用戶就方便多了:


# @param data-file
# @param unit
# @param select-time [option]
function pick_time()
{
    local file="$1"
    local unit="$2"
    local selecttime=""
    if [ $# -gt 2 ]; then 
        selecttime="$3"
    fi

    local n=0 
    local m=0
    local line=""
    local stamp=0
    local time=()
    local value=https://www.cnblogs.com/goodcitizen/archive/2022/04/20/()
    local match=-1
    while read line; do
        if ["$(($n%2))" -eq 0 ]; then 
            # time field at event line
            stamp=$(($line/1000))

            if [ ${is_macos} -eq 1 ]; then 
                time[$m]=$(date -j -r "$stamp" "+%Y%m%d%H")
                #time[$m]=$(date -j -f "%Y%m%d%H" -r "$stamp")
            else
                time[$m]=$(date -d "@$stamp" "+%Y%m%d%H")
            fi

            if [ -z "$selecttime" ]; then 
                if [ ${is_macos} -eq 1 ]; then 
                    # macos builtin echo does not recognize -n
                    #echo "$stamp"
                    /bin/echo -n "$m:    ${time[$m]}    "
                else 
                    echo -n "$m:    ${time[$m]}    "
                fi
            else
                if [ "$selecttime" = "${time[$m]}" ]; then 
                    # match selected time
                    picked_time="$selecttime"
                    match=$m
                fi
            fi
        else
            # value field at odd line
            value[$m]="$line"

            if [ -z "$selecttime" ]; then 
                if [ "${unit:0:1}" = ' ' ]; then 
                    # unit start with a space means no byte transfer
                    echo "${value[$m]}${unit}"
                else 
                    # append their unit after B/KB/MB/GB
                    format_bytes "${value[$m]}"
                fi
            else
                if [ $match -eq $m ]; then 
                    # the value is what we want
                    picked_value="https://www.cnblogs.com/goodcitizen/archive/2022/04/20/${value[$m]}"
                    #echo "matched: $picked_value"
                fi
            fi
            m=$(($m+1))
        fi
        n=$(($n+1))
    done < $file

    if [ $n -lt 2 ]; then 
        echo "no data"
        picked_time=0
        picked_value=https://www.cnblogs.com/goodcitizen/archive/2022/04/20/0
        return 1
    fi

    if [ -z"$selecttime" ]; then 
        # only prompt user when:
        #    1. no time provided
        read -p "pick a time to record (-1 to quit): " n
        if [ $n -lt 0 ]; then 
            exit 1
        fi 

        picked_time="${time[$n]}"
        picked_value="https://www.cnblogs.com/goodcitizen/archive/2022/04/20/${value[$n]}"

        echo "pick ${picked_time}    ${picked_value}"
    elif [ $match -eq -1 ]; then 
        # only prompt user when:
        #    2. provided time but no match
        #
        # call myself without 2nd parameter to make prompt
        echo "given time ${selecttime} not find"
        pick_time "$file" "$unit"
    else
        echo "pick ${picked_time}    ${picked_value}"
    fi
}

腳本接受的三個引數分別是:

  • data-file:上面 json 過濾掉括號后的結果,key、value 交替存放,key 位于奇數行,value 位于偶數行
  • unit:展示給用戶的數值單位,如 B/KB/MB/GB ...
  • selecttime:用戶選擇的時刻

pick_time 用于三個場景:

  • 沒有提供第三個引數 (selecttime) 時,展示整個串列,供用戶選擇;
  • 提供了 selecttime 且有資料匹配時,回傳匹配的資料;
  • 提供了 selecttime 但沒有資料匹配時,展示整個串列,供用戶重新選擇;

第一次展示時走的是場景一;后面再展示時走的是場景三;場景二一般不會出現,只有當后臺服務回傳的資料集缺失了資料時才會命中,可以起到提醒用戶的效果,避免資料不一致,對整個腳本做個簡單說明:

  • 主體就是一個回圈遍歷 json 資料源 (去除括號)
  • 根據奇偶行將 key 和 value 分別放入 stamp() 和 value() 陣列
  • 時間戳單位為毫秒,需要轉換到 epoch (整點),再基于 date 命令把 unix time 轉換為 YYYYMMDDHH 的形式,注意 mac 和 linux 上的 date 命令有差異,需要分平臺處理
  • 沒有給定 selecttime 時,列印轉換為時間字串的 key,這里使用 echo -n 來避免換行,因為緊接著要列印 value 部分,注意 mac 和 linux 上的 echo 命令有差異,需要分平臺處理  (mac 上的 bultin echo 不識別 -n 引數,需要呼叫 echo 命令)
  • 如果給定了 selecttime,進行對比,若匹配則記錄用戶選擇的索引至 match 中,用于稍后的 value 匹配
  • 處理 value 時也是差不多的邏輯:不給定 selecttime 就輸出 value 的值和單位;給定 selecttime 且當前索引匹配 match 值,則記錄 value 至 picked_value,這是一個全域變數,稍后可以讓呼叫函式參考來獲取結果
  • 回圈結束后,若未給定 selecttime,要求用戶輸入索引來選擇一個時間,記錄對應的 time 和 value 至 picked_time 與 picked_value
  • 若給定 selecttime 但未能匹配,再次呼叫兩引數的自己,來列印全部資料供用戶選擇
  • 若給定 selecttime 匹配了,列印用戶選擇時間的對應值

一般 value 的單位是位元組,遇到流量這種上 T 級別的資料,直接給展示給用戶一長串資料非常沒有可讀性,通過 format_bytes 可以將位元組按數量自動轉換為合適的單位 (B/KB/MB/GB):

# @brief format size into B/KB/MB/GB according to bytes amount
# @param size
function format_bytes()
{
    local size="$1"
    echo "${size}" | awk '{ if ($1 <= 1024) { print $1 " B" } else if ($1 <= 1024*1024) { print $1/1024 " KB" } else if ($1 <= 1024*1024*1024) { print $1/1024/1024 " MB"} else { print $1/1024/1024/1024 " GB"} }'
}

這是一個基于 awk 的實作,除了位元組單位,還有一些是百分比,如果對它們也進行轉換就鬧錯誤了,可以在這種單位前給一個空格來避免這種轉換,例如 " %",

最終的效果可以看下面這段輸出:

$ sh fetch_iyuntu.sh -v '3.0.0.112' -p 'pc'
request flow ok
0:    2022011519    1487.74 GB
1:    2022011520    1563.08 GB
2:    2022011521    1636.64 GB
3:    2022011522    1618.64 GB
4:    2022011523    1443.26 GB
5:    2022011600    1205.57 GB
6:    2022011601    1096.95 GB
7:    2022011602    905.025 GB
8:    2022011603    654.045 GB
9:    2022011604    482.283 GB
10:    2022011605    391.029 GB
11:    2022011606    355.738 GB
12:    2022011607    390.631 GB
13:    2022011608    589.954 GB
14:    2022011609    827.38 GB
15:    2022011610    1050.17 GB
16:    2022011611    1242.52 GB
17:    2022011612    1348.08 GB
18:    2022011613    1405.8 GB
19:    2022011614    1472.75 GB
20:    2022011615    1492.5 GB
pick a time to record (-1 to quit): 

觀察串列,發現昨天晚上 21 點是流量高峰,輸入索引 2 來收集對應的指標 (場景一);后面就不需要用戶再選擇了,腳本會自動匹配 2022011521 的時刻去選擇其它指標資料 (場景三);如果某個指標的資料串列沒有 2022011521 這個時刻,腳本會自動列出指標的全部時刻供用戶重新選擇 (場景二),一般是由于后臺發版資料缺失了 (資料量太大算不過來,偶爾發生),一般輸入 -1 退出腳本重新選擇一個其它時刻再跑一遍,

把上面的都結合在一起,就是一個完整的拉取和決議總流量的程序啦:

# @param: starttime
# @param: endtime
# @param: version
# @param: clienttype
# @param: cookie
# @param: select-time [option]
function fetch_flow()
{
    local starttime="$1"
    local endtime="$2"
    local version="$3"
    local clienttype="$4"
    local cookie="$5"
    local selecttime=""
    if [ $# -gt 5 ]; then 
        selecttime="$6"
    fi

    local data="https://www.cnblogs.com/goodcitizen/archive/2022/04/20/start=${starttime}&end=${endtime}&method=p2pflow&version=${version}&vipLevel=all&clusterItem=cluster_hour&clientType=${clienttype}"
    curl -s "http://${HOST}/client/api/xxxxxxxxxxxx/" -H "Accept: */*" -H "Connection: keep-alive" -H "Content-Type: application/x-www-form-urlencoded; charset=UTF-8" -H "Accept-Encoding: gzip, deflate" -H "Origin: ${HOST}" -H "Referer: http://${HOST}/stability/main?typeName2=total_flow&typeName2=cluster_hour&typeName2=${clienttype}&datepicker0=${starttime}&datepicker1=${endtime}" -H "X-Requested-With: XMLHttpRequest" --cookie "$cookie"  -d "$data" --output temp.gzip

    if [ $? -eq 0 ]; then 
        echo "request flow ok"
        cat temp.gzip | gunzip > temp.txt
        # to see if 'null'
        if [ $? -eq 0 -a "$(head -c 4 temp.txt)" != "null" ]; then 
            jq -r '."總流量"[]|.[0],.[1]' temp.txt > data.txt
            pick_time "data.txt" "" "$selecttime"
        else 
            echo "null"
        fi
    else 
        echo "request flow failed"
    fi 
}

增加了以下內容:

  • 當選擇的時間范圍內沒有任何資料時,temp.txt 將僅包含四個字符:null,這可以通過 head 截取來判斷,沒有資料時直接輸出 null 并跳過這個指標的獲取
  • jq 決議"總流量"維度并將資料寫入 data.txt 檔案中
  • pick_time 從 data.txt 檔案中獲取資料,由于第一次請求總流量 (version=pc-all) 時 selecttime 還為空,所以它僅展示串列,當它回傳后用戶已經選好了時刻;如果是請求版本流量 (version=3.0.0.112) selecttime 不為空,將直接從 data.txt 中選擇對應時刻的資料并記錄在 picked_value 中,供后面使用

至此,完成了第一個指標從拉取資料、決議內容到獲取指標資料的全程序,

聯接起來

有了第一個,第二、第三……就容易多了,copy & paste 再改改就行了,來看下腳本 main 函式是如何聯接并驅動這一切的:

function main()
{
    local version=""
    local endtime=$(date +%s)
    local starttime=0
    local cookiefile="cookie.txt"
    local clienttype="android"

    #os="${OSTYPE/"darwin"//}"
    local OSTYPE=$(uname -s)
    local os="${OSTYPE/"Darwin"//}"
    if [ "$os" != "$OSTYPE" ]; then 
        # darwin: macos
        is_macos=1
    fi

    while getopts 'hv:p:s:e:c:' flag; do
        case "${flag}" in
            v) 
                version="${OPTARG}" 
                ;;
            p)
                clienttype="${OPTARG}"
                ;;
            s) 
                if [ ${is_macos} -eq 1 ]; then 
                    # convert date as some format to stamp only supported on mac
                    starttime=$(date -j -f "%Y%m%d%H" "${OPTARG}" "+%s")
                else 
                    # date on linux only recongnize date part..
                    # so: 2021090914 = 20210909 + 14 * 3600
                    starttime=$(date -d "$((${OPTARG}/100))" "+%s")
                    starttime=$((${starttime}+${OPTARG}%100*3600))
                fi

                if [ $? -ne 0 ]; then 
                    echo "convert starttime failed"
                    return 1
                fi
                ;;
            e) 
                if [ ${is_macos} -eq 1 ]; then 
                    # convert date as some format to stamp only supported on mac
                    endtime=$(date -j -f "%Y%m%d%H" "${OPTARG}" "+%s")
                else
                    # date on linux only recongnize date part..
                    # so: 2021090914 = 20210909 + 14 * 3600
                    endtime=$(date -d "$((${OPTARG}/100))" "+%s")
                    endtime=$((${endtime}+${OPTARG}%100*3600))
                fi

                if [ $? -ne 0 ]; then 
                    echo "convert endtime failed"
                    return 1
                fi
                ;;
            c) 
                cookiefile="${OPTARG}" 
                ;;
            h|*) 
                echo "Usage: sh fetch_iyuntu.sh -v version [-h] [-s starttime(YYYYMMDDHH)] [-e endtime(YYYYMMDDHH)] [-c cookiefile] [-p platform]"
                return 0
                ;;
        esac
    done

    if [ -z "$version" ]; then 
        echo "no version specified"
        return 1
    fi

    echo "query data for platform: ${clienttype}"
    if [ $starttime -eq 0 ]; then 
        starttime=$(($endtime-86400))  # default 1 day ago
    fi

    echo "starttime: $starttime, endtime: $endtime"
    if [ "$endtime" -lt "$starttime" ]; then 
        echo "invalid start & end time"
        return 1
    fi

    if [ ! -f "${cookiefile}" ]; then 
        echo "cookie file not find: ${cookiefile}"
        return 1
    fi 

    local cookie=$(cat ${cookiefile})
    #echo "cookie: $cookie"

    # 1. fetch total flow first to determine time
    fetch_flow "$starttime" "$endtime" "${clienttype}-all" "${clienttype}" "$cookie"
    select_time="$picked_time"
    total_flow="$picked_value"

    # 2. fetch flow of that version
    fetch_flow "$starttime" "$endtime" "$version" "${clienttype}" "$cookie" "$select_time"
    version_flow="$picked_value"

    # 3. fetch slow speed ratio
    fetch_slow_speed "$starttime" "$endtime" "$version" "${clienttype}" "$cookie" 
    slow_speed="$picked_value"

    # 4. fetch nat connectivity 
    fetch_nat_connectivity "$starttime" "$endtime" "$version" "${clienttype}" "$cookie" 
    nat_connectivity="$picked_value"

    # 5. fetch total download speed, 0 - normal user
    fetch_total_download_speed "$starttime" "$endtime" "$version" "${clienttype}" "$cookie" "0" "$select_time"
    total_download_speed_for_normal_user="$picked_value"

    # 6. fetch total download speed, 2 - svip user
    fetch_total_download_speed "$starttime" "$endtime" "$version" "${clienttype}" "$cookie" "2" "$select_time"
    total_download_speed_for_svip_user="$picked_value"

    # 7. fetch vod result success, 0 - normal user
    fetch_vod_result_success "$starttime" "$endtime" "$version" "${clienttype}" "$cookie" "0" "$select_time"
    vod_result_success_for_normal_user="$picked_value"

    # 8. fetch vod result success, 2 - svip user
    fetch_vod_result_success "$starttime" "$endtime" "$version" "${clienttype}" "$cookie" "2" "$select_time"
    vod_result_success_for_svip_user="$picked_value"

    # 9. fetch vod p2p share ratio, 0 - normal user
    fetch_vod_p2p_share_ratio "$starttime" "$endtime" "$version" "${clienttype}" "$cookie" "0" "$select_time"
    vod_p2p_share_ratio_for_normal_user="$picked_value"

    #10. fetch vod p2p share ratio, 2 - svip user
    fetch_vod_p2p_share_ratio "$starttime" "$endtime" "$version" "${clienttype}" "$cookie" "2" "$select_time"
    vod_p2p_share_ratio_for_svip_user="$picked_value"

    if [ "${clienttype}" != "pc" ]; then 
        #11. fetch ts download speed, 0 - normal user
        fetch_ts_download_speed "$starttime" "$endtime" "$version" "${clienttype}" "$cookie" "0" "$select_time"
        ts_download_speed_for_normal_user="$picked_value"

        #12. fetch ts download speed, 2 - svip user
        fetch_ts_download_speed "$starttime" "$endtime" "$version" "${clienttype}" "$cookie" "2" "$select_time"
        ts_download_speed_for_svip_user="$picked_value"

        #13. fetch ts result success, 0 - normal user
        fetch_ts_result_success "$starttime" "$endtime" "$version" "${clienttype}" "$cookie" "0" "$select_time"
        ts_result_success_for_normal_user="$picked_value"

        #14. fetch ts result success, 2 - svip user
        fetch_ts_result_success "$starttime" "$endtime" "$version" "${clienttype}" "$cookie" "2" "$select_time"
        ts_result_success_for_svip_user="$picked_value"

        #15. fetch ts p2p share ratio, 0 - normal user, 3 - downloading
        fetch_ts_p2p_share_ratio "$starttime" "$endtime" "$version" "${clienttype}" "$cookie" "0" "3" "$select_time"
        ts_p2p_share_ratio_for_normal_user="$picked_value"

        #16. fetch ts p2p share ratio, 2 - svip user, 3 - downloading
        fetch_ts_p2p_share_ratio "$starttime" "$endtime" "$version" "${clienttype}" "$cookie" "2" "3" "$select_time"
        ts_p2p_share_ratio_for_svip_user="$picked_value"
    else 
        # pc use ts playing share ratio instead of ts downloading
        #11. fetch ts p2p share ratio, 2 - svip user, 1 - playing
        fetch_ts_p2p_share_ratio "$starttime" "$endtime" "$version" "${clienttype}" "$cookie" "2" "1" "$select_time"
        ts_p2p_share_ratio_for_svip_user="$picked_value"
    fi

    #17. fetch ts flow ratio
    fetch_ts_play_flow "$starttime" "$endtime" "$version" "${clienttype}" "$cookie" "$select_time"
    local ts_play_flow_version="$picked_value"
    fetch_ts_play_flow "$starttime" "$endtime" "${clienttype}-all" "${clienttype}" "$cookie" "$select_time"
    local ts_play_flow_all="$picked_value"
    ts_play_flow_ratio=$(echo "${ts_play_flow_version},${ts_play_flow_all}" | awk -F',' '{print $1*100/$2}')

    print_statistic "${clienttype}"
}

做個簡單說明:

  • 腳本可以接收 6 個引數
    • -v version,指定收集資料指標的版本
    • -p platform,指定收集資料的端,默認為 android,可以指定 pc 或其它端
    • -e endtime,指定結束時間,格式為 YYYYMMDDHH,精確到小時,如果不指定,默認為當前時間
    • -s starttime,指定開始時間,格式同上,如果不指定,默認為結束時間前推 24 小時
    • -c cookiefile,指定 cookie 內容,默認為  cookie.txt
    • -h,輸出 usage
    • 注:mac 上可以直接將字串 YYYYMMDDHH 轉換為 unix time;linux 上不能,需要兩步,第一步轉換為到日期的時間戳,第二步加上小時數
  • 完整性檢查,沒有版本號、cookie 檔案、結束時間小于開始時間等都是致命錯誤,直接退出
  • fetch_flow 獲取總流量,記錄用戶選擇時間 (select_time) 和總流量 (total_flow)
  • 分別獲取各個分量
    • fetch_flow 獲取版本流量 (version_flow)
    • fetch_slow_speed 獲取慢速比 (slow_speed)
    • fetch_nat_connectivity 獲取 NAT 連通率 (nat_connectivity)
    • fetch_total_download_speed 分別獲取普通 (total_download_speed_for_normal_user) 和 svip 用戶總速度 (total_download_speed_for_svip_user)
    • fetch_vod_result_success 分別獲取普通 (vod_result_success_for_normal_user) 和 svip 用戶原畫下載成功率 (vod_result_success_for_svip_user)
    • fetch_vod_p2p_share_ratio 分別獲取普通 (vod_p2p_share_ratio_for_normal_user) 和 svip 用戶原畫下載分享率 (vod_p2p_share_ratio_for_svip_user)
    • fetch_ts_download_speed 分別獲取普通 (ts_download_speed_for_normal_user) 和 svip 用戶轉碼下載速度 (ts_download_speed_for_svip_user)
    • fetch_ts_result_success 分別獲取普通 (ts_result_success_for_normal_user) 和 svip 用戶轉碼下載成功率 (ts_result_success_for_svip_user)
    • fetch_ts_p2p_share_ratio 分別獲取普通 (ts_share_ratio_for_normal_user) 和 svip 用戶轉碼下載分享率 (ts_share_ratio_for_svip_user)
    • fetch_ts_play_slow 分別獲取版本 (ts_play_flow_version) 和總的轉碼播放流量 (ts_play_flow_all),最后計算出轉碼播放流量占比 (ts_play_flow_ration)
    • 注:pc 端轉碼指標只有 svip 轉碼播放分享率 (ts_share_ratio_for_svip_user)
  • 最后列印獲取到的指標資料 (print_statistic)

在每個 fetch_xxx 函式獲取指標資料后都跟著一個賦值操作,將 pick_value 放入對應的全域變數中,在最后列印指標資訊時 (print_statistic) 會用到它們:

# @param: clienttype
function print_statistic()
{
    local clienttype="$1"
    local flow_ratio="0"
    local result_success="0"
    echo "======================================="
    echo "total flow:                       $(format_bytes ${total_flow})"
    echo "version flow:                     $(format_bytes ${version_flow})"

    # eat divided by zero error
    flow_ratio=$(echo "${version_flow},${total_flow}" | awk -F',' '{print $1*100/$2}' 2>/dev/null)
    echo "flow ratio:                       ${flow_ratio} %"
    echo "slow speed:                       ${slow_speed} %"
    echo "nat connectivity:                 ${nat_connectivity} %"
    echo "total download speed (normal)     $(format_bytes ${total_download_speed_for_normal_user})/s"
    echo "total download speed (svip)       $(format_bytes ${total_download_speed_for_svip_user})/s"
    result_success=$(echo "${vod_result_success_for_normal_user}" | awk '{print $1*100}')
    echo "vod result success (normal)       ${result_success} %"
    result_success=$(echo "${vod_result_success_for_svip_user}" | awk '{print $1*100}')
    echo "vod result success (svip)         ${result_success} %"
    echo "vod p2p share ratio (normal)      ${vod_p2p_share_ratio_for_normal_user} %"
    echo "vod p2p share ratio (svip)        ${vod_p2p_share_ratio_for_svip_user} %"

    # pc has vod downloading & ts playing & NO ts downloading
    if [ "${clienttype}" != "pc" ]; then 
        echo "ts download speed (normal)        $(format_bytes ${ts_download_speed_for_normal_user})/s"
        echo "ts download speed (svip)          $(format_bytes ${ts_download_speed_for_svip_user})/s"
        result_success=$(echo "${ts_result_success_for_normal_user}" | awk '{print $1*100}')
        echo "ts result success (normal)        ${result_success} %"
        result_success=$(echo "${ts_result_success_for_svip_user}" | awk '{print $1*100}')
        echo "ts result success (svip)          ${result_success} %"
        echo "ts p2p share ratio (normal)       ${ts_p2p_share_ratio_for_normal_user} %"
        echo "ts p2p share ratio (svip)         ${ts_p2p_share_ratio_for_svip_user} %"
    else 
        echo "ts play share ratio (svip)        ${ts_p2p_share_ratio_for_svip_user} %"
    fi

    echo "ts play flow ratio                ${ts_play_flow_ratio} %"
}

這個函式還負責計算版本流量占比,注意這里采用了 awk 來進行浮點運算,shell 內建的運算只支持整型,

最后來看下運行效果吧:

=======================================
total flow:                       1636.64 GB
version flow:                     xxxx.xx GB
flow ratio:                       xx.xxxx %
slow speed:                       x.xxxxx %
nat connectivity:                 xx.xxxx %
total download speed (normal)     x.xxxxx MB/s
total download speed (svip)       xx.xxxx MB/s
vod result success (normal)       xx.xxxx %
vod result success (svip)         xx.xxxx %
vod p2p share ratio (normal)      xx.xx %
vod p2p share ratio (svip)        xx.xx %
ts play share ratio (svip)        xx.xx %
ts play flow ratio                xx.xxxx %

自從有了這個腳本,填個灰度發版指標就是分分鐘的事兒了,程式員的效率又有提升,節約下的時間又可以愉快的摸魚了~

結語

本文介紹了一種使用 shell 腳本自動獲取發版指標資料的方法,主要有以下幾個關鍵點:

  • curl 基于瀏覽器 cookie 訪問 web 服務器獲取指標資料
  • jq 決議復雜 json 格式資料
  • pick_time 從 key-value 串列中提取某個時刻的指標值

其中第二點又是關鍵中的關鍵,之前也用 jq 做過 json 資料的決議,但處理這樣復雜的 json 形式還是頭一遭,當時差點就在這里卡殼了,對 jq 語法還需要系統的學習一下,不然遇到更復雜的資料形式,可能又要卡殼 ??,

說一下工具與效率的問題,在比較強調流程的公司干活,不斷在作業中積累一些工具、腳本是非常必要的,不然隨著作業量的加碼,個人精力會被消耗在日常重復作業中,導致效率降低,同樣一件事,剛入職時花一個小時搞定,入職幾年了還需要差不多的時間來搞定,絕對需要考慮下有沒有優化空間,把一些流程化的、可自動化的作業提煉出來用腳本、工具完成,會極大的節約時間、保證準確性并將注意力集中于該集中的地方,這就是所謂的工欲善其事、必先利其器吧,

后記

這個腳本總體上已經很方便了,美中不足的地方是前面提到的獲取瀏覽器 cookie,如何自動登錄 web 并記錄 cookie?這個我又有一系列探索,后面會寫成一篇單獨的文章分享出來,

參考

[1]. Shell:jq 回圈 json 物件, jq 回圈 json 陣列, jq 用法實踐, jq converts a JSON object to key=value, jq parses one field from an JSON array into bash array

[2]. shell編程學習之使用jq對json提取

[3]. linux工具之jq

[4]. mac date命令

[5]. Linux date命令時間戳和時間之間的轉換

[6]. linux shell實作亂數多種方法(date,random,uuid)

 

轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/459482.html

標籤:其他

上一篇:Linux:檔案解壓、復制和移動的若干坑

下一篇:Linux 配置免密登錄

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • CA和證書

    1、在 CentOS7 中使用 gpg 創建 RSA 非對稱密鑰對 gpg --gen-key #Centos上生成公鑰/密鑰對(存放在家目錄.gnupg/) 2、將 CentOS7 匯出的公鑰,拷貝到 CentOS8 中,在 CentOS8 中使用 CentOS7 的公鑰加密一個檔案 gpg -a ......

    uj5u.com 2020-09-10 00:09:53 more
  • Kubernetes K8S之資源控制器Job和CronJob詳解

    Kubernetes的資源控制器Job和CronJob詳解與示例 ......

    uj5u.com 2020-09-10 00:10:45 more
  • VMware下安裝CentOS

    VMware下安裝CentOS 一、軟硬體準備 1 Centos鏡像準備 1.1 CentOS鏡像下載地址 下載地址 1.2 CentOS鏡像下載程序 點擊下載地址進入如下圖的網站,選擇需要下載的版本,這里選擇的是Centos8,點擊如圖所示。 決定選擇Centos8后,選擇想要的鏡像源進行下載,此 ......

    uj5u.com 2020-09-10 00:12:10 more
  • 如何使用Grep命令查找多個字串

    如何使用Grep 命令查找多個字串 大家好,我是良許! 今天向大家介紹一個非常有用的技巧,那就是使用 grep 命令查找多個字串。 簡單介紹一下,grep 命令可以理解為是一個功能強大的命令列工具,可以用它在一個或多個輸入檔案中搜索與正則運算式相匹配的文本,然后再將每個匹配的文本用標準輸出的格式 ......

    uj5u.com 2020-09-10 00:12:28 more
  • git配置http代理

    git配置http代理 經常遇到克隆 github 慢的問題,這里記錄一下幾種配置 git 代理的方法,解決 clone github 過慢。 目錄 git配置代理 git單獨配置github代理 git配置全域代理 配置終端環境變數 git配置代理 主要使用 git config 命令 git單獨 ......

    uj5u.com 2020-09-10 00:12:33 more
  • Linux npm install 裝包時提示Error EACCES permission denied解

    npm install 裝包時提示Error EACCES permission denied解決辦法 ......

    uj5u.com 2020-09-10 00:12:53 more
  • Centos 7下安裝nginx,使用yum install nginx,提示沒有可用的軟體包

    Centos 7下安裝nginx,使用yum install nginx,提示沒有可用的軟體包。 18 (flaskApi) [root@67 flaskDemo]# yum -y install nginx 19 已加載插件:fastestmirror, langpacks 20 Loading ......

    uj5u.com 2020-09-10 00:13:13 more
  • Linux查看服務器暴力破解ssh IP

    在公網的服務器上經常遇到別人爆破你服務器的22埠,用來挖礦或者干其他嘿嘿嘿的事情~ 這種情況下正確的做法是: 修改默認ssh的22埠 使用設定密鑰登錄或者白名單ip登錄 建議服務器密碼為復雜密碼 創建普通用戶登錄服務器(root權限過大) 建立堡壘機,實作統一管理服務器 統計爆破IP [root ......

    uj5u.com 2020-09-10 00:13:17 more
  • CentOS 7系統常見快捷鍵操作方式

    Linux系統中一些常見的快捷方式,可有效提高操作效率,在某些時刻也能避免操作失誤帶來的問題。 ......

    uj5u.com 2020-09-10 00:13:31 more
  • CentOS 7作業系統目錄結構介紹

    作業系統存在著大量的資料檔案資訊,相應檔案資訊會存在于系統相應目錄中,為了更好的管理資料資訊,會將系統進行一些目錄規劃,不同目錄存放不同的資源。 ......

    uj5u.com 2020-09-10 00:13:35 more
最新发布
  • vim的常用命令

    Vim的6種基本模式 1. 普通模式在普通模式中,用的編輯器命令,比如移動游標,洗掉文本等等。這也是Vim啟動后的默認模式。這正好和許多新用戶期待的操作方式相反(大多數編輯器默認模式為插入模式)。 2. 插入模式在這個模式中,大多數按鍵都會向文本緩沖中插入文本。大多數新用戶希望文本編輯器編輯程序中一 ......

    uj5u.com 2023-04-20 08:43:21 more
  • vim的常用命令

    Vim的6種基本模式 1. 普通模式在普通模式中,用的編輯器命令,比如移動游標,洗掉文本等等。這也是Vim啟動后的默認模式。這正好和許多新用戶期待的操作方式相反(大多數編輯器默認模式為插入模式)。 2. 插入模式在這個模式中,大多數按鍵都會向文本緩沖中插入文本。大多數新用戶希望文本編輯器編輯程序中一 ......

    uj5u.com 2023-04-20 08:42:36 more
  • docker學習

    ###Docker概述 真實專案部署環境可能非常復雜,傳統發布專案一個只需要一個jar包,運行環境需要單獨部署。而通過Docker可將jar包和相關環境(如jdk,redis,Hadoop...)等打包到docker鏡像里,將鏡像發布到Docker倉庫,部署時下載發布的鏡像,直接運行發布的鏡像即可。 ......

    uj5u.com 2023-04-19 09:26:53 more
  • 設定Windows主機的瀏覽器為wls2的默認瀏覽器

    這里以Chrome為例。 1. 準備作業 wsl是可以使用Windows主機上安裝的exe程式,出于安全考慮,默認情況下改功能是無法使用。要使用的話,終端需要以管理員權限啟動。 我這里以Windows Terminal為例,介紹如何默認使用管理員權限打開終端,具體操作如下圖所示: 2. 操作 wsl ......

    uj5u.com 2023-04-19 09:25:49 more
  • docker學習

    ###Docker概述 真實專案部署環境可能非常復雜,傳統發布專案一個只需要一個jar包,運行環境需要單獨部署。而通過Docker可將jar包和相關環境(如jdk,redis,Hadoop...)等打包到docker鏡像里,將鏡像發布到Docker倉庫,部署時下載發布的鏡像,直接運行發布的鏡像即可。 ......

    uj5u.com 2023-04-19 09:19:04 more
  • Linux學習筆記

    IP地址和主機名 IP地址 ifconfig可以用來查詢本機的IP地址,如果不能使用,可以通過install net-tools安裝。 Centos系統下ens33表示主網卡;inet后表示IP地址;lo表示本地回環網卡; 127.0.0.1表示代指本機;0.0.0.0可以用于代指本機,同時在放行設 ......

    uj5u.com 2023-04-18 06:52:01 more
  • 解決linux系統的kdump服務無法啟動的問題

    問題:專案麒麟系統服務器的kdump服務無法啟動,沒有相關日志無法定位問題。 1、查看服務狀態是關閉的,重啟系統也無法啟動 systemctl status kdump 2、修改grub引數,修改“crashkernel”為“512M(有的機器數值太大太小都會導致報錯,建議從128M開始試,或者加個 ......

    uj5u.com 2023-04-12 09:59:50 more
  • 解決linux系統的kdump服務無法啟動的問題

    問題:專案麒麟系統服務器的kdump服務無法啟動,沒有相關日志無法定位問題。 1、查看服務狀態是關閉的,重啟系統也無法啟動 systemctl status kdump 2、修改grub引數,修改“crashkernel”為“512M(有的機器數值太大太小都會導致報錯,建議從128M開始試,或者加個 ......

    uj5u.com 2023-04-12 09:59:01 more
  • 你是不是暴露了?

    作者:袁首京 原創文章,轉載時請保留此宣告,并給出原文連接。 如果您是計算機相關從業人員,那么應該經歷不止一次網路安全專項檢查了,你肯定是收到過資訊系統技術檢測報告,要求你加強風險監測,確保你提供的系統服務堅實可靠了。 沒檢測到問題還好,檢測到問題的話,有些處理起來還是挺麻煩的,尤其是線上正在運行的 ......

    uj5u.com 2023-04-05 16:52:56 more
  • 細節拉滿,80 張圖帶你一步一步推演 slab 記憶體池的設計與實作

    1. 前文回顧 在之前的幾篇記憶體管理系列文章中,筆者帶大家從宏觀角度完整地梳理了一遍 Linux 記憶體分配的整個鏈路,本文的主題依然是記憶體分配,這一次我們會從微觀的角度來探秘一下 Linux 內核中用于零散小記憶體塊分配的記憶體池 —— slab 分配器。 在本小節中,筆者還是按照以往的風格先帶大家簡單 ......

    uj5u.com 2023-04-05 16:44:11 more