1. 为什么要做这个东西?
最开始做性能测试,用的是 Loadrunner,服务器大都为 Linux 和 AIX,那时候的监控方法就是 nmon,先执行 nmon,然后导出到 Windows 上来用 excel 分析,操作太麻烦,后来自己折腾了一个基于 Perl 的 nmon 结果文件分析工具,可直接在 Linux,AIX,HPUX 中执行,直接出结果。(可见:nmon 结果分析脚本)
前年的时候,发现性能测试需要监控的机器越来越多,10 台服务器已经是很常见了,这么多机器,执行 nmon 是件很麻烦的事情,且很难与测试场景同步执行。后来接触到 HP 的 SiteScope,发现这个东西真是烂到家了,不过,LR 和 SiteScope 的接口倒可以用一用。后来断断续续开始折腾。目前已经实现了 Linux、AIX、Windows、Android 这几类系统。主要用 python 实现,AIX 中用的 perl,Windows 中是同事帮忙用 C# 写的。
2. 先从安卓开始,先看怎么用。
先配置好 adb 环境,修改脚本中的 app 包名和设备的 udid 信息。然后通过 usb 连接好手机,执行 start 的批处理或者 sh 文件。启动完成后,后台服务开始启动。
可通过 2 种方式来进行访问。
1:通过浏览器。如下图。默认端口 8888,可修改。
2:通过 LoadRunner,如下图。
增加监控项后,通过 LR 的场景,可以实时获取到性能数据。如下图:
3:脚本:
脚本部分使用 python 实现,使用了 bottle 的 web 框架,代码量很小,默认为监听 8888 端口,默认为 5s 获取一次数据,前端的 get 请求一次,返回一次结果(如果 1s get 一次,结果是不变的。),监控项包括了系统的 cpu,内存,app 的 cpu,内存,对应的网络流量(流量为累积值),大部分为通过 proc 目录中的文件进行读取和分析。
#!/usr/bin/env python
# -*- coding: utf-8 -*-
__author__ = 'among,lifeng29@163.com'
__version__ = '1.0,20160322'
__license__ = 'copy left'
import platform
import socket
from time import sleep
from bottle import *
# android app and device info
PKG_NAME = 'com.xxx.xxx'
UDID = 'xxxxxxxxxxx'
# init
interval = 5
port = 8888
socket.setdefaulttimeout(10)
pt_name = platform.platform()
if pt_name.startswith('Windows'):
GREP = 'findstr'
else:
GREP = 'grep'
# start
cpu_temp = {"idle": None, "user": None, "system": None, "iowait": None, "total": None, "cpu_app": None}
cpu_result = {"cpu_Total": None, "cpu_User": None, "cpu_Sys": None, "cpu_iowait": None, "cpu_app": None}
mem_result = {"MemTotal": None, "MemUsage": None, "Buffers": None, "Cached": None, "MemUsage_pct": None}
other_result = {"Native_heap": None, "Dalvik_heap": None, "Mem_app": None, "Network_rx": None, "Network_tx": None,
"Network_all": None}
pkg_info = {"pkg_name": PKG_NAME, "pid": None, "uid": None, "userId": None, "UDID": UDID}
# function start
# ex_cmd
def ex_cmd(cmd):
res = list()
try:
fh = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.DEVNULL)
fh.wait()
for line in fh.stdout.readlines():
line = line.decode().strip()
if line != '':
res.append(line)
fh.stdout.close()
except Exception as ex:
print('run cmd exception: %s' % ex)
res.append('error')
return res
# get package pid ...
def get_pkg():
global pkg_info
pid_file = ex_cmd('adb -s %s shell ps|%s %s' % (pkg_info['UDID'], GREP, pkg_info['pkg_name']))
if len(pid_file) > 0:
pkg_info['uid'], pkg_info['pid'] = pid_file[0].split()[0:2]
else:
pkg_info['pid'] = None
pkg_info['uid'] = None
# print(pkg_info)
# get proc file
def Read_Proc(file):
proc_file = ex_cmd('adb -s %s shell cat /proc/%s' % (pkg_info['UDID'], file))
return proc_file
# get memory
def And_Memory():
global mem_result
Proc_MemInfo = Read_Proc('meminfo')
if len(Proc_MemInfo) == 0:
for x in mem_result:
mem_result[x] = None
return None
Memory = dict()
for i in Proc_MemInfo:
Memory[i.split()[0][0:-1]] = int(i.split()[1])
MemTotal = Memory['MemTotal']
MemFree = Memory['MemFree']
Buffers = Memory['Buffers']
Cached = Memory['Cached']
# memusage calc
MemUsage = MemTotal - MemFree - Buffers - Cached
# result value
mem_result['MemTotal'] = MemTotal
mem_result['MemUsage'] = MemUsage
mem_result['Buffers'] = Buffers
mem_result['Cached'] = Cached
mem_result['MemUsage_pct'] = ('%.3f' % (float(MemUsage) * 100 / MemTotal))
# get system cpu
def And_Cpu():
global cpu_result
global cpu_temp
Cpu_data = Read_Proc('stat')
if len(Cpu_data) == 0:
for x in cpu_temp:
cpu_temp[x] = None
for x in cpu_result:
cpu_result[x] = None
return None
Cpu_data = Cpu_data[0].split()
app_cpu1 = None
if pkg_info['pid'] is not None:
Cpu_app_data = Read_Proc('%s/stat' % pkg_info['pid'])[0].split()
if len(Cpu_app_data) > 20:
app_cpu1 = float(Cpu_app_data[13]) + float(Cpu_app_data[14]) + float(Cpu_app_data[15]) + float(
Cpu_app_data[16])
idle = float(Cpu_data[4])
user = float(Cpu_data[1]) + float(Cpu_data[2])
system = float(Cpu_data[3]) + float(Cpu_data[6]) + float(Cpu_data[7])
iowait = float(Cpu_data[5])
total = idle + user + system + iowait
if cpu_temp['idle'] is not None:
cpu_result_temp = dict()
cpu_per = total - cpu_temp['total']
cpu_result_temp['cpu_User'] = str("%.3f" % ((user - cpu_temp['user']) / cpu_per * 100))
cpu_result_temp['cpu_Sys'] = str("%.3f" % ((system - cpu_temp['system']) / cpu_per * 100))
cpu_result_temp['cpu_iowait'] = str("%.3f" % ((iowait - cpu_temp['iowait']) / cpu_per * 100))
cpu_result_temp['cpu_Total'] = str("%.3f" % ((cpu_per - idle + cpu_temp['idle']) / cpu_per * 100))
if cpu_temp['cpu_app'] is not None and app_cpu1 is not None:
cpu_result_temp['cpu_app'] = str("%.3f" % ((app_cpu1 - cpu_temp['cpu_app']) / cpu_per * 100))
if float(cpu_result_temp['cpu_app']) < 0:
cpu_result_temp['cpu_app'] = None
else:
cpu_result_temp['cpu_app'] = None
if float(cpu_result_temp['cpu_Total']) < 0 or float(cpu_result_temp['cpu_User']) < 0 or float(
cpu_result_temp['cpu_Sys']) < 0:
# print('zero###############################')
return None
else:
cpu_result = cpu_result_temp
cpu_temp['idle'] = idle
cpu_temp['user'] = user
cpu_temp['system'] = system
cpu_temp['iowait'] = iowait
cpu_temp['total'] = total
cpu_temp['cpu_app'] = app_cpu1
# get other
def And_Other():
global other_result
if pkg_info['pid'] is None:
for x in other_result:
other_result[x] = None
else:
global mem_result
# mem calc
ad_mem_info = ex_cmd('adb -s %s shell dumpsys meminfo %s' % (pkg_info['UDID'], pkg_info['pkg_name']))
if len(ad_mem_info) > 5:
for info in ad_mem_info:
info = info.split()
if info[0] == 'Native':
if info[1].isdigit():
other_result['Native_heap'] = info[1]
elif info[1] == 'Heap':
other_result['Native_heap'] = info[2]
elif info[0] == 'Dalvik':
if info[1].isdigit():
other_result['Dalvik_heap'] = info[1]
elif info[1] == 'Heap':
other_result['Dalvik_heap'] = info[2]
elif info[0] == 'TOTAL':
other_result['Mem_app'] = info[1]
break
# net user calc
net_user_info = ex_cmd(
'adb -s %s shell cat /proc/net/xt_qtaguid/stats |%s %s' % (pkg_info['UDID'], GREP, pkg_info["userId"]))
if len(net_user_info) > 0:
rx_b = 0
tx_b = 0
for xx in net_user_info:
all_net = xx.split()
rx_b += int(all_net[5])
tx_b += int(all_net[7])
rxtx = rx_b + tx_b
other_result['Network_rx'] = rx_b
other_result['Network_tx'] = tx_b
other_result['Network_all'] = rxtx
def mon_run():
while True:
thrs = list()
t0 = threading.Thread(target=get_pkg)
t0.start()
thrs.append(t0)
t1 = threading.Thread(target=And_Memory)
t1.start()
thrs.append(t1)
t2 = threading.Thread(target=And_Cpu)
t2.start()
thrs.append(t2)
t3 = threading.Thread(target=And_Other)
t3.start()
thrs.append(t3)
for t in thrs:
t.join()
sleep(interval)
def mon_page():
return template('android', MemTotal=mem_result['MemTotal'], MemUsage=mem_result['MemUsage'],
Buffers=mem_result['Buffers'], Cached=mem_result['Cached'],
MemUsage_pct=mem_result['MemUsage_pct'], Native_heap=other_result['Native_heap'],
Dalvik_heap=other_result['Dalvik_heap'], Mem_app=other_result['Mem_app'],
cpu_Total=cpu_result['cpu_Total'], cpu_User=cpu_result['cpu_User'], cpu_Sys=cpu_result['cpu_Sys'],
cpu_iowait=cpu_result['cpu_iowait'], cpu_app=cpu_result['cpu_app'],
Network_rx=other_result['Network_rx'], Network_tx=other_result['Network_tx'],
Network_all=other_result['Network_all'])
# get app userid
userid_info = ex_cmd('adb -s %s shell dumpsys package %s|%s userId' % (pkg_info['UDID'], PKG_NAME, GREP))
if len(userid_info) > 0:
tpinfo = userid_info[0].split()
pkg_info["userId"] = tpinfo[0].split('=')[1]
th_all = threading.Thread(target=mon_run)
th_all.setDaemon(True)
th_all.start()
app = Bottle()
# monitor
@app.route('/SiteScope/cgi/go.exe/SiteScope')
@app.route('/monitor')
def monitor():
return mon_page()
run(app=app, host='0.0.0.0', port=port, reloader=False, quiet=True, debug=True)
代码写的比较烂,算是功能实现了。
以上仅是针对安卓的代码,其他系统的都与此类似。
协议本身比较简单,就是通过 http 协议传输 xml 格式的数据,如果自己搞个前端,可以做出更好的样式。
通过 LR 也可以在测试过程中同步记录数据到 LR 的结果中。
3. 其他平台的实现
AIX 中,基于 Perl(AIX 中不预装 python),使用方式一致。
Linux,基于 Python,使用方式一致。
Windows,同事帮忙基于 C# 实现的,可做为系统服务,使用方式一致。
全部的代码在点击这里
4. 效果
目前满足了我们性能测试过程中,大量服务器端监控的需求,系统监控的工作量大大降低,对于其他工具,如 jmeter,有熟悉看能否集成使用。
代码中可能还存在一些 bug,欢迎大家讨论。