Shell腳本 | 性能測試以內存

性能測試中,內存是一個不可或缺的方面。好比說在跑 Monkey 的過程當中,如何準確持續的獲取到內存數據就顯得尤其重要。shell

今天分享一個腳本,能夠在給定時間內持續監控內存,最後輸出成一份 CSV 文件,經過 Excel 的插入圖表功能能夠造成一副內存走勢圖。數組

腳本中最關鍵的兩個步驟以下,其他看代碼吧(註釋很詳細):bash

  1. 經過 adb 命令獲取內存文件
  2. 經過 Python 腳本解析內存文件,取出其中的 "TOTAL" 值

run.sh微信

#!/usr/bin/env bash
# Description: 獲取內存TOTAL值
# How to use: sh +x run.sh <package_name> <time>
# 新建輸出文件夾
function init_data() {
    if [[ ! -d ${OUTPUT} ]];then
        mkdir -p ${OUTPUT}
    fi
    touch ${MEMINFO_FILE}
}
# 將日期追加入MEMINFO_FILE
# 將內存信息追加入MEMINFO_FILE
function dump_memory_info() {
    echo "TIME FLAG:"  `date "+%Y-%m-%d %H:%M:%S"` >> ${1}
    adb shell dumpsys meminfo ${2} >> ${1}
}
# 每隔一分鐘拉取一次內存信息
function start_monitor() {
    for((i=1;i<=${1};i++));
    do
        dump_memory_info ${2} ${3}
        sleep 60
    done
}
# 處理"TOTAL:"格式的內存文件
# 調用report腳本,傳入參數MEMINFO_FILE
# 將logs/csv/t_u.csv文件拷貝並重命名爲MEMINFO_CSV_FILE
# 刪除"logs"文件夾,減小硬盤空間佔用
function report_with_colon() {
    sh +x report.sh ${1}
    cp -p logs/csv/t_u.csv ${2}
    rm -r logs
}
# 處理"TOTAL"格式的內存文件
function report_without_colon() {
    sh +x report_no_colon.sh ${1}
    cp -p logs/csv/t_u.csv ${2}
    rm -r logs
}
# 調用report腳本,輸出csv文件
function report_memory_info() {
    TOTAL_TIME=$(cat ${1} | grep "TOTAL:" -c)
    if [[ ${TOTAL_TIME} != 0 ]]; then
        report_with_colon ${1} ${2}
    else
        report_without_colon ${1} ${2}
    fi
}
# 運行腳本時傳入的第一個參數:包名
PACKAGE_NAME=$1
# 第二個參數:運行時間(分鐘)
TIME=$2
# 絕對路徑
WORKSPACE=`pwd`
# 輸出文件夾
OUTPUT=${WORKSPACE}/output_memory
# 內存文件
MEMINFO_FILE=${OUTPUT}/meminfo.txt
MEMINFO_CSV_FILE=${OUTPUT}/meminfo.csv
# 刪除"output_memory",避免數據混淆
if [[ -d "output_memory" ]]; then
    rm -r output_memory
fi
# 開始調用方法
init_data
start_monitor ${TIME} ${MEMINFO_FILE} ${PACKAGE_NAME}
report_memory_info ${MEMINFO_FILE}  ${MEMINFO_CSV_FILE}

report.sh性能

#!/usr/bin/env bash
# Description: 提取meminfo.txt中的TOTAl值並輸出到CSV文件(適用於'TOTAL:'格式的內存文件)
# 根據dumpsys meminfo後的文件中不一樣的標籤, 設定文件名
# 由於標籤諸如'.ttf mmap'等, 中間有空格, 不適合直接作文件名
getMemFileName()
{
  local tag=$1
  case ${tag} in
    "Native")
    fileName="native_meminfo.txt"
      ;;
    "Dalvik")
    fileName="dalvik_meminfo.txt"
      ;;
    "Cursor")
    fileName="cursor_meminfo.txt"
      ;;
    "Other dev")
    fileName="otherdev_meminfo.txt"
      ;;
    "Ashmem")
    fileName="ashmem_meminfo.txt"
      ;;
    ".so mmap")
    fileName="so_meminfo.txt"
      ;;
    ".jar mmap")
    fileName="jar_meminfo.txt"
      ;;
    ".apk mmap")
    fileName="apk_meminfo.txt"
      ;;
    ".ttf mmap")
    fileName="ttf_meminfo.txt"
      ;;
    ".dex mmap")
    fileName="dex_meminfo.txt"
      ;;
    "Other mmap")
    fileName="other_meminfo.txt"
      ;;
    "Unknown")
    fileName="unknown_meminfo.txt"
      ;;
    "TOTAL:")
    fileName="total_meminfo.txt"
      ;;
  *)
      ;;
  esac
  echo ${fileName}
}
# 解析MonkeyTest完成後的meminfo.txt
# 按列讀取, 1, 2, 3, 4, 5列分別對應:Pss, SharedDirty, PrivateDirty, HeapSize, HeapFree
splitMeminfo()
{
  local fileName=$1
  # 刪除VALUE字符串中以分隔符「.」匹配的右邊字符,保留左邊字符。${VALUE%.*}
  local folderName=${fileName%.*}
  mkdir logs/${folderName}
  awk '{print $1}' logs/${fileName} > logs/${folderName}/Pss
  awk '{print $2}' logs/${fileName} > logs/${folderName}/SharedDirty
  awk '{print $3}' logs/${fileName} > logs/${folderName}/PrivateDirty
  awk '{print $4}' logs/${fileName} > logs/${folderName}/HeapSize
  awk '{print $5}' logs/${fileName} > logs/${folderName}/HeapFree
}
# 將MonkeyTest完成後的meminfo.txt中的tag去掉
# 如: PSS 234 222 333 555 0 -> 234 222 333 555 0
# 緣由:統一成5列數據, 方便'splitMeminfo'按列讀取數據
removeTag()
{
  local fileName=$1
  local tag=$2

  case ${tag} in
    "Native")
    # 刪除第一列,而後輸出到logs/native.txt
    awk '{$1="";print}' ${fileName} > logs/native.txt
    splitMeminfo native.txt
      ;;
    "Dalvik")
    awk '{$1="";print}' ${fileName} > logs/dalvik.txt
    splitMeminfo dalvik.txt
      ;;
    "Cursor")
    awk '{$1="";print}' ${fileName} > logs/cursor.txt
    splitMeminfo cursor.txt
      ;;
    "Other dev")
    awk '{$1=""; $2="";print}' ${fileName} > logs/otherdev.txt
    splitMeminfo otherdev.txt
      ;;
    "Ashmem")
    awk '{$1="";print}' ${fileName}  > logs/ashmem.txt
    splitMeminfo ashmem.txt
      ;;
    ".so mmap")
    awk '{$1=""; $2="";print}' ${fileName} > logs/sommap.txt
    splitMeminfo sommap.txt
      ;;
    ".jar mmap")
    awk '{$1=""; $2="";print}' ${fileName} > logs/jarmmap.txt
    splitMeminfo jarmmap.txt
      ;;
    ".apk mmap")
    awk '{$1=""; $2="";print}' ${fileName} > logs/apkmmap.txt
    splitMeminfo apkmmap.txt
      ;;
    ".ttf mmap")
    awk '{$1=""; $2="";print}' ${fileName} > logs/ttfmmap.txt
    splitMeminfo ttfmmap.txt
      ;;
    ".dex mmap")
    awk '{$1="";$2="";print}' ${fileName} > logs/dexmmap.txt
    splitMeminfo dexmmap.txt
      ;;
    "Other mmap")
    awk '{$1="";$2="";print}' ${fileName} > logs/othermmap.txt
    splitMeminfo othermmap.txt
      ;;
    "Unknown")
    awk '{$1="";print}' ${fileName}  > logs/unknown.txt
    splitMeminfo unknown.txt
      ;;
    "TOTAL:")
    awk '{$1="";print}' ${fileName}  > logs/total.txt
    splitMeminfo total.txt
      ;;
  *)
      ;;
  esac
}
# 生成.csv文件, 方便網頁中用js讀取, 並傳值給HighCharts
# 將splitMeminfo中生成的多個文件, 列轉行
# 格式:Pss, 234,333,444,556,444......
getCSVFile()
{
  mkdir logs/csv
  local meminfo_Files=("Pss" "SharedDirty" "PrivateDirty" "HeapSize" "HeapFree")
    # 數組長度
  local count=${#meminfo_Files[@]}
  for((i=0;i<$count;i++))
  do
    local item=${meminfo_Files[$i]}
    echo "Categories" >> logs/csv/${item}.csv
    for data in `find ./ -name "${item}"`
    do
        # 刪除VALUE字符串中以分隔符「.」匹配的右邊字符,保留左邊字符。${VALUE%.*}
        seriesName=${data%/*}
        # 刪除VALUE字符串中以分隔符「.」匹配的左邊字符,保留右邊字符。${VALUE##*.}
        seriesName=${seriesName##*/}
      csvline=${seriesName}
      for line in `cat ${data}`
      do
        csvline=${csvline},${line}
      done
      echo ${csvline} >> logs/csv/${item}.csv
      sed -i '' "s/,//g" logs/csv/${item}.csv
    done
  done
}
# 第一列的全部參數
MEMINFO_ARGS=("Native" "Dalvik" "Cursor" "Other dev"  "Ashmem" ".so mmap" ".jar mmap" ".apk mmap" ".ttf mmap" ".dex mmap" "Other mmap" "Unknown" "TOTAL:")
# 從run.sh傳入的參數
MEMINFO_File=${1}
# MEMINFO_ARGS的長度(length)
count=${#MEMINFO_ARGS[@]}
# 建立logs/, 用以存放日誌
mkdir logs
# 解析日誌
for((i=0;i<$count;i++));
do
  # 調用getMemFileName方法,傳入參數MEMINFO_ARGS,返回文件名
  fileName=`getMemFileName "${MEMINFO_ARGS[$i]}"`
  # 輸出包含${MEMINFO_ARGS[$i]}的行
  awk /"${MEMINFO_ARGS[$i]}"/'{print}' ${MEMINFO_File} > logs/${fileName}
  removeTag logs/${fileName} "${MEMINFO_ARGS[$i]}"
done
# 將分析過的日誌轉換成csv文件
getCSVFile
# 將時間取出來放到logs/time文件中
grep 'TIME FLAG:' ${MEMINFO_File} > logs/logtime
cat logs/logtime | while read line
do
  echo ${line#*:} >> logs/time
done
# 處理完全部行,輸出行數
line_count=`awk 'END{print NR}' logs/total/Pss`
# 提取時間和TOTAL值,輸出到t_u.csv文件
echo "Time,TOTAL" > logs/csv/t_u.csv
for ((j=1;j<=${line_count};j++));
do
  total_mem=`tail -n ${j} logs/total/Pss | head -n 1`
  time_mem=`tail -n ${j} logs/time | head -n 1`
  echo "${time_mem},${total_mem}" >> logs/csv/t_u_bk.csv
done
line_count=`awk 'END{print NR}' logs/csv/t_u_bk.csv`
for ((k=1;k<=${line_count};k++));
do
  total_line=`tail -n ${k} logs/csv/t_u_bk.csv | head -n 1`
    echo "$total_line" >> logs/csv/t_u.csv
done

注意:
部分手機獲取到的內存文件會同時包含 "TOTAL" 和 "TOTAL:" 字段,經過替換 report.sh 腳本中的 "TOTAL" 進行區分便可測試


歡迎關注微信公衆號"測試開發Stack"日誌

相關文章
相關標籤/搜索