最近在做性能监控移动端的方面的研究,把遇到的问题总结下:
- 尽量不要用 split 切割的方式取值,因为不同手机取值不一样,还可能会出现数组越界
- 取 cpu 占用情况时,不要用 top 和 dump cpuinfo,因为他们拿到的是几个 cpu 的值,会出现超过 100%
取 men 占用情况
cmd = "adb -s " + devices +" shell dumpsys meminfo %s" % (pkg_name)
print(cmd)
output = subprocess.check_output(cmd).split()
s_men = ".".join([x.decode() for x in output]) # 转换为string
print(s_men)
men2 = int(re.findall("TOTAL.(d+)*", s_men, re.S)[0])
print(men2 )
取电量占用情况
def get_battery(devices):
cmd = "adb -s " + devices + " shell dumpsys battery"
print(cmd)
output = subprocess.check_output(cmd).split()
stderr=subprocess.PIPE).stdout.readlines()
st = ".".join([x.decode() for x in output]) # 转换为string
print(st)
battery2 = int(re.findall("level:.(d+)*", st, re.S)[0])
writeInfo(battery2, PATH("../info/" + devices + "_battery.pickle"))
return battery2
取 wifi,gprs 上下行流量
def get_flow(pid, type, devices):
# pid = get_pid(pkg_name)
_flow1 = [[], []]
if pid is not None:
cmd = "adb -s " + devices + " shell cat /proc/" + pid + "/net/dev"
print(cmd)
_flow = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE,
stderr=subprocess.PIPE).stdout.readlines()
for item in _flow:
if type == "wifi" and item.split()[0].decode() == "wlan0:": # wifi
# 0 上传流量,1 下载流量
_flow1[0].append(int(item.split()[1].decode()))
_flow1[1].append(int(item.split()[9].decode()))
print("------flow---------")
print(_flow1)
break
if type == "gprs" and item.split()[0].decode() == "rmnet0:": # gprs
print("-----flow---------")
_flow1[0].append(int(item.split()[1].decode()))
_flow1[1].append(int(item.split()[9].decode()))
print(_flow1)
break
else:
_flow1[0].append(0)
_flow1[1].append(0)
得到 cpu 几核
def get_cpu_kel(devices):
cmd = "adb -s " + devices + " shell cat /proc/cpuinfo"
print(cmd)
output = subprocess.check_output(cmd).split()
sitem = ".".join([x.decode() for x in output]) # 转换为string
return len(re.findall("processor", sitem))
取 cpu 使用情况
'''
每一个cpu快照均
'''
def totalCpuTime(devices):
user=nice=system=idle=iowait=irq=softirq= 0
'''
user:从系统启动开始累计到当前时刻,处于用户态的运行时间,不包含 nice值为负进程。
nice:从系统启动开始累计到当前时刻,nice值为负的进程所占用的CPU时间
system 从系统启动开始累计到当前时刻,处于核心态的运行时间
idle 从系统启动开始累计到当前时刻,除IO等待时间以外的其它等待时间
iowait 从系统启动开始累计到当前时刻,IO等待时间(since 2.5.41)
irq 从系统启动开始累计到当前时刻,硬中断时间(since 2.6.0-test4)
softirq 从系统启动开始累计到当前时刻,软中断时间(since 2.6.0-test4)
stealstolen 这是时间花在其他的操作系统在虚拟环境中运行时(since 2.6.11)
guest 这是运行时间guest 用户Linux内核的操作系统的控制下的一个虚拟CPU(since 2.6.24)
'''
cmd = "adb -s " + devices +" shell cat /proc/stat"
print(cmd)
p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE, shell=True)
(output, err) = p.communicate()
res = output.split()
for info in res:
if info.decode() == "cpu":
user = res[1].decode()
nice = res[2].decode()
system = res[3].decode()
idle = res[4].decode()
iowait = res[5].decode()
irq = res[6].decode()
softirq = res[7].decode()
print("user=" + user)
print("nice=" + nice)
print("system=" + system)
print("idle=" + idle)
print("iowait=" + iowait)
print("irq=" + irq)
print("softirq=" + softirq)
result = int(user) + int(nice) + int(system) + int(idle) + int(iowait) + int(irq) + int(softirq)
print("totalCpuTime"+str(result))
return result
'''
每一个进程快照
'''
def processCpuTime(pid, devices):
'''
pid 进程号
utime 该任务在用户态运行的时间,单位为jiffies
stime 该任务在核心态运行的时间,单位为jiffies
cutime 所有已死线程在用户态运行的时间,单位为jiffies
cstime 所有已死在核心态运行的时间,单位为jiffies
'''
utime=stime=cutime=cstime = 0
cmd = "adb -s "+ devices + " shell cat /proc/" + pid +"/stat"
print(cmd)
p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
stdin=subprocess.PIPE, shell=True)
(output, err) = p.communicate()
res = output.split()
utime = res[13].decode()
stime = res[14].decode()
cutime = res[15].decode()
cstime = res[16].decode()
print("utime="+utime)
print("stime="+stime)
print("cutime="+cutime)
print("cstime="+cstime)
result = int(utime) + int(stime) + int(cutime) + int(cstime)
print("processCpuTime="+str(result))
return result
'''
计算某进程的cpu使用率
100*( processCpuTime2 – processCpuTime1) / (totalCpuTime2 – totalCpuTime1) (按100%计算,如果是多核情况下还需乘以cpu的个数);
cpukel cpu几核
pid 进程id
'''
def cpu_rate(pid, cpukel, devices):
# pid = get_pid(pkg_name)
processCpuTime1 = processCpuTime(pid, devices)
time.sleep(1)
processCpuTime2 = processCpuTime(pid, devices)
processCpuTime3 = processCpuTime2 - processCpuTime1
totalCpuTime1 = totalCpuTime(devices)
time.sleep(1)
totalCpuTime2 = totalCpuTime(devices)
totalCpuTime3 = (totalCpuTime2 - totalCpuTime1)*cpukel
cpu = 100 * (processCpuTime3) / (totalCpuTime3)
print(cpu)
得到 fps
'''
@author fenfenzhong
'''
def get_fps(pkg_name, devices):
_adb = "adb -s " + devices +" shell dumpsys gfxinfo %s" % pkg_name
print(_adb)
results = os.popen(_adb).read().strip()
frames = [x for x in results.split('n') if validator(x)]
frame_count = len(frames)
jank_count = 0
vsync_overtime = 0
render_time = 0
for frame in frames:
time_block = re.split(r's+', frame.strip())
if len(time_block) == 3:
try:
render_time = float(time_block[0]) + float(time_block[1]) + float(time_block[2])
except Exception as e:
render_time = 0
'''
当渲染时间大于16.67,按照垂直同步机制,该帧就已经渲染超时
那么,如果它正好是16.67的整数倍,比如66.68,则它花费了4个垂直同步脉冲,减去本身需要一个,则超时3个
如果它不是16.67的整数倍,比如67,那么它花费的垂直同步脉冲应向上取整,即5个,减去本身需要一个,即超时4个,可直接算向下取整
最后的计算方法思路:
执行一次命令,总共收集到了m帧(理想情况下m=128),但是这m帧里面有些帧渲染超过了16.67毫秒,算一次jank,一旦jank,
需要用掉额外的垂直同步脉冲。其他的就算没有超过16.67,也按一个脉冲时间来算(理想情况下,一个脉冲就可以渲染完一帧)
所以FPS的算法可以变为:
m / (m + 额外的垂直同步脉冲) * 60
'''
if render_time > 16.67:
jank_count += 1
if render_time % 16.67 == 0:
vsync_overtime += int(render_time / 16.67) - 1
else:
vsync_overtime += int(render_time / 16.67)
_fps = int(frame_count * 60 / (frame_count + vsync_overtime))
# return (frame_count, jank_count, fps)
print("-----fps------")
print(_fps)
取值 cpu 参考资料
- How do I get the total CPU usage of an application from /proc/pid/stat
- Why is the CPU usage reported by top in Linux over 100%
- PROC 系列之---/proc/pid/stat 详解
如何测试一个软件的功能本文转自于TesterHome,如有侵权请联系(2523030730@qq.com)删除。文章源自玩技e族-https://www.playezu.com/216533.html 文章源自玩技e族-https://www.playezu.com/216533.html
未知地区 1F
@author fenfenzhong
说下当年我趟过的坑:
1、最直接的抓取性能数据一轮的执行效率问题和占用设备问题
(1)设备端后台 shell 脚本作为监控脚本,即避免了 adb 通讯效率及异常风险又不用额外长连接 pc
(2)dumpsy meminfo 取进程内存时额外 cpu 开销大,会导致 dumpsys cpu 不准;系统自带 top 命令单次获取数据时间长且精度不够,最后用的 busybox 所带的 top 解决(在获取内存数据前先取 cpu)
(3)抓取所有进程 pss 耗时问题,最后是直接取 dumpsys meminfo 把其统计好的数据通过文本处理规则全部格式化为 csv
(4)处理各个命令获取数据结果的截取和格式化为 csv 保存的效率问题——利用 grep,sed,awk 文本流处理的形式提高处理效率,详细的截取、计算、统计规则使用 awk 语法搞定
2、关于抓取数据的种类和必要性
(1)系统内存/proc/meminfo——整体宏观判定总内存,额外重点关注下 anon(匿名内存)使用
(2)dumpsys meminfo 统计结果中也有总内存统计,在做单进程 pss 抓取时顺便处理
(3)全部进程抓取外,额外重点关注进程也有监控详细 heap 数据需求,在抓所有进程 pss 时排除所关注的进程 pss,在之后使用 dumpsys meminfo 进程 pid 再次单进程获取(取 heap 数据以外,目前额外关注了 view 数量,子进程数量,fd 数量,vsz 大小则在 ps 中获取),view 不回收持续增长是验收 app 测试时很常见的问题,fd 和 vsz 主要针对 32 位平台地址空间有限,持续分配会引起可用 3G 地址空间用尽
(4)fps 测试需求,由于我这边需要站在用户体验角度分析问题,而不是代码执行效率是否满足(换言之动效设计等设定的帧间隔时间等设计因素;硬件性能掉帧也得考虑)——自己设计的统计规则使用 dumpsys SurfaceFlinger –latency surface 名 计算帧率
(5)流量和电量只是单独专项需求另行设计脚本子进程抓取
3、关于数据呈现如何利于快速分析
(1)指定时间段统计间隔(1 小时),计算峰值降序,内存额外计算 pss 极值差 (最大 – 最小) 降序排列,引导快速选择重点关注的进程
(2)highcharts 交互图做报告展示模板,PID 变化时刻标红点,同名进程标黄点
4、关于监控脚本应用场景,毕竟监控配合 case 操作才是完整方案
(1)监控脚本子进程执行,可被用例执行脚本统一管理开始结束时刻及监控结果存储位置
(2)单项监控数据获取独立函数,可按需任意组合或集成到其他执行脚本环节中
(3)报告展示逻辑部分,可集成到其他展示报告中
5、关于时间轴统一:
(1)/proc/timer_list NOW 所在行纳秒时间作为统一时间轴对比 getevent、vsync(fps 时间轴)
(2)/proc/uptime 开机已运行时间作为 cpu、内存、电量、流量、sim 卡信号强度等数据的时间轴
(3)date 命令获取的时间作为额外记录用于对应 log 时间点
最近在做外部录像、uid 流量,显示占率,getevent 操作统一时间轴对比分析的方案。(https://testerhome.com/topics/4775)
(https://testerhome.com/topics/3685)
(https://testerhome.com/topics/2976)
早期的分享,当然到现在我这自己内部用的已经有迭代变化。不过思路上和关键函数变化不大,只是做些优化,更多的还是在方案组合使用衍生更多应用场景上的扩展。感谢分享,你的这个知识很复杂。。特别是 shell 那块,完全看不懂 按目标需求逻辑捋顺流程,awk 这部分我是为了不再另写文件,写为了一行命令,这部分另存后在分号后换行看,大体语法和 c 的相似,只是需要了解下 awk 的公共变量。你好,问一下 validator 这个有什么作用最近也发现了发现了 split 取值的问题!学习了!我可以做伸手党吗?哈哈有个疑问,为什么在获取 totalCpuTime1 = totalCpuTime(devices) 在后面取,应该前面和 processCpuTime1 = processCpuTime(pid, devices) 一起取更加合适吧。我也好奇这个问题为什么,我测了几台机器循环 10 多次,得到的 CPU 数值基本都在 10 以下,很多都是 1.几这样?亲,内存那块获取到的数据是 KB?fps 获取到的数据一直是 60@lose 请教一个问题:
当 cpu 为多核的时候,核数应该是 乘到这里?cpu = 100 * (processCpuTime3) / (totalCpuTime3) 而不是 totalCpuTime3 = (totalCpuTime2 – totalCpuTime1)*cpukel 这里?cpu_rate 返回只有 1 点几,2 点几的样子,和实际不符啊