扒开源安卓性能测试工具moblieperf源码——开发属于你自己的性能稳定性测试工具
moblieperf下载和使用
moblieperf由阿里巴巴开源的Android性能测试工具
下载:官方源码地址mobileperf github
使用:
- 使用pycharm打开下载的项目
- 使用只需要修改配置文件config.conf即可
- 运行采集:a.mac、linux 在mobileperf工具根目录下执行sh run.sh ; b.windows 双击run.bat
配置图:(简单使用只需要修改包名和设备序列号即可)

源码阅读
原来我们主要阅读我们想实现功能是如何实现的(cpu,内存)
我们先从启动类StartUp中run方法看起:
for i in range(0,5):
if self.device.adb.is_connected(self.serialnum):
is_device_connect = True
break
else:
logger.error("device not found:"+self.serialnum)
time.sleep(2)
self.serialnum就是配置表中的设备序列号,这一段是用过ADB类中的静态方法is_connected实现的,我们来看一下他们是怎么检查配置中的手机是否链接
@staticmethod
def is_connected(device_id):
'''
检查设备是否连接上
'''
if device_id in ADB.list_device():
return True
else:
return False
@staticmethod
def list_device():
'''获取设备列表 :return: 返回设备列表
:rtype: list
'''
proc = subprocess.Popen("adb devices", stdout=subprocess.PIPE, shell=True)
result = proc.stdout.read()
if not isinstance(result, str):
result = result.decode('utf-8')
result = result.replace('\r', '').splitlines()
logger.debug("adb devices:")
logger.debug(result)
device_list = []
for device in result[1:]:
if len(device) <= 1 or not '\t' in device: continue
if device.split('\t')[1] == 'device':
# 只获取连接正常的
device_list.append(device.split('\t')[0])
return device_list
通过这一段代码我们可以发现,mobileperf是基于adb命令去读取相应的信息,而他的使用是通过subprocess类中的Popen去操作adb命令,通过adb devices命令判断是否已经链接成功
注:subprocess是python标准库兼容性较好,且是非阻塞性执行,在输入输出的拓展性、错误处理都比较健全完善,后面会在提到这个方法使用。
# 对是否安装被测app的检查 只在最开始检查一次
if not self.device.adb.is_app_installed(self.packages[0]):
logger.error("test app not installed:" + self.packages[0])
return
try:
#初始化数据处理的类,将没有消息队列传递过去,以便获取数据,并处理
# datahandle = DataWorker(self.get_queue_dic())
# 将queue传进去,与datahandle那个线程交互
self.add_monitor(CpuMonitor(self.serialnum, self.packages, self.frequency, self.timeout))
self.add_monitor(MemMonitor(self.serialnum, self.packages, self.frequency, self.timeout))
这里我们节选了一部分代码,当我们知道他基本实现逻辑其实我们大致也可以猜到is_app_installed判断app是否安装他是如何实现的!应该也是通过adb命令去寻找已安装的包是否存在
def is_app_installed(self, package):
'''
判断app是否安装
'''
if package in self.list_installed_app():
return True
else:
return False
def list_installed_app(self):
'''
获取已安装app列表
:return: 返回app列表
:rtype: list
'''
result = self.run_shell_cmd('pm list packages')
result = result.replace('\r', '').splitlines()
logger.debug(result)
installed_app_list = []
for app in result:
if not 'package' in app: continue
if app.split(':')[0] == 'package':
# 只获取连接正常的
installed_app_list.append(app.split(':')[1])
logger.debug(installed_app_list)
return installed_app_list
正如我们所料,他是用过命令adb shell pm list packages 返回的接口进行切割拿到包名,去判断需要测试包名是否在其中
那么他是采集和收集内存/cpu等信息是不是也是这样实现的呢,答案是肯定的
end_time = time.time() + self._timeout
cpu_title = ["datetime", "device_cpu_rate%", "user%", "system%","idle%"]
cpu_file = os.path.join(RuntimeData.package_save_path, 'cpuinfo.csv')
for i in range(0, len(self.packages)):
cpu_title.extend(["package", "pid", "pid_cpu%"])
if len(self.packages) > 1:
cpu_title.append("total_pid_cpu%")
try:
with open(cpu_file, 'a+') as df:
csv.writer(df, lineterminator='\n').writerow(cpu_title)
except RuntimeError as e:
logger.error(e)
while not self._stop_event.is_set() and time.time() < end_time:
try:
logger.debug("---------------cpuinfos, into _collect_package_cpu_thread loop thread is : " + str(threading.current_thread().name))
before = time.time()
#为了cpu值的准确性,将采集的时间间隔放在top命令中了
cpu_info = self._top_cpuinfo()
after = time.time()
time_consume = after - before
logger.debug(" ============== time consume for cpu info : "+str(time_consume))
这里我们截取了一段收集cpu的代码,可以发现他是通过一个while循环去调用_top_cpuinfo方法
def _top_cpuinfo(self):
self._top_pipe = self.device.adb.run_shell_cmd(self.top_cmd, sync=False)
out = self._top_pipe.stdout.read()
error = self._top_pipe.stderr.read()
我们接着往下看
def run_shell_cmd(self, cmd, **kwds):
'''执行 adb shell 命令
'''
# 如果失去连接后,adb又正常连接了
if not self.before_connect and self.after_connect:
cpu_uptime_file = os.path.join(RuntimeData.package_save_path, "uptime.txt")
with open(cpu_uptime_file, "a+",encoding = "utf-8") as writer:
writer.write(TimeUtils.getCurrentTimeUnderline() + " /proc/uptime:" + self.run_adb_cmd("shell cat /proc/uptime") + "\n")
self.before_connect = True
ret = self.run_adb_cmd('shell', '%s' % cmd, **kwds)
# 当 adb 命令传入 sync=False时,ret是Poen对象
if ret == None:
logger.error(u'adb cmd failed:%s ' % cmd)
return ret
不难发现,最终依旧是调用的run_adb_cmd方法去执行的adb命令,最后把收集到数据写入对应文件中,看到这里我们大概可以自己实现一下了
开发自己的安卓性能工具
注:这里工具页面开发我们使用gui开发工具pyside6
前面我们提到了标准库subprocess这里介绍一下我们会用到的两个方法communicate从子进程的stdout和stderr读取数据。这个方法会阻塞主程序,直到子进程完成,poll检查子进程是否结束,如果子进程正常退出,输出应该为 0;否则为非0值
页面简单拖拽

ui文件转换py文件
pyside6-uic <ui文件名>.ui -o <py文件名>.py
# -*- coding: utf-8 -*- ################################################################################
## Form generated from reading UI file 'performanceTools.ui'
##
## Created by: Qt User Interface Compiler version 6.6.1
##
## WARNING! All changes made in this file will be lost when recompiling UI file!
################################################################################ from PySide6.QtCore import (QCoreApplication, QDate, QDateTime, QLocale,
QMetaObject, QObject, QPoint, QRect,
QSize, QTime, QUrl, Qt)
from PySide6.QtGui import (QBrush, QColor, QConicalGradient, QCursor,
QFont, QFontDatabase, QGradient, QIcon,
QImage, QKeySequence, QLinearGradient, QPainter,
QPalette, QPixmap, QRadialGradient, QTransform)
from PySide6.QtWidgets import (QApplication, QHBoxLayout, QLabel, QLineEdit,
QPushButton, QSizePolicy, QVBoxLayout, QWidget) class Ui_Form(object):
def setupUi(self, Form):
if not Form.objectName():
Form.setObjectName(u"Form")
Form.resize(400, 300)
self.horizontalLayoutWidget = QWidget(Form)
self.horizontalLayoutWidget.setObjectName(u"horizontalLayoutWidget")
self.horizontalLayoutWidget.setGeometry(QRect(20, 90, 201, 80))
self.horizontalLayout = QHBoxLayout(self.horizontalLayoutWidget)
self.horizontalLayout.setObjectName(u"horizontalLayout")
self.horizontalLayout.setContentsMargins(0, 0, 0, 0)
self.label = QLabel(self.horizontalLayoutWidget)
self.label.setObjectName(u"label") self.horizontalLayout.addWidget(self.label) self.packagename = QLineEdit(self.horizontalLayoutWidget)
self.packagename.setObjectName(u"packagename") self.horizontalLayout.addWidget(self.packagename) self.verticalLayoutWidget = QWidget(Form)
self.verticalLayoutWidget.setObjectName(u"verticalLayoutWidget")
self.verticalLayoutWidget.setGeometry(QRect(230, 30, 160, 231))
self.verticalLayout = QVBoxLayout(self.verticalLayoutWidget)
self.verticalLayout.setObjectName(u"verticalLayout")
self.verticalLayout.setContentsMargins(0, 0, 0, 0)
self.cold_time_cal_button = QPushButton(self.verticalLayoutWidget)
self.cold_time_cal_button.setObjectName(u"cold_time_cal_button") self.verticalLayout.addWidget(self.cold_time_cal_button) self.cold_time_cal_result_button = QPushButton(self.verticalLayoutWidget)
self.cold_time_cal_result_button.setObjectName(u"cold_time_cal_result_button") self.verticalLayout.addWidget(self.cold_time_cal_result_button) self.cpuinfo_button = QPushButton(self.verticalLayoutWidget)
self.cpuinfo_button.setObjectName(u"cpuinfo_button") self.verticalLayout.addWidget(self.cpuinfo_button) self.cpuinfo_result_button = QPushButton(self.verticalLayoutWidget)
self.cpuinfo_result_button.setObjectName(u"cpuinfo_result_button") self.verticalLayout.addWidget(self.cpuinfo_result_button) self.retranslateUi(Form) QMetaObject.connectSlotsByName(Form)
# setupUi def retranslateUi(self, Form):
Form.setWindowTitle(QCoreApplication.translate("Form", u"Form", None))
self.label.setText(QCoreApplication.translate("Form", u"\u8bf7\u8f93\u5165\u5305\u540d", None))
self.cold_time_cal_button.setText(QCoreApplication.translate("Form", u"\u51b7\u542f\u52a8\u5e73\u5747\u65f6\u95f4\u7edf\u8ba1", None))
self.cold_time_cal_result_button.setText(QCoreApplication.translate("Form", u"\u51b7\u542f\u52a8\u65f6\u95f4\u7ed3\u679c\u67e5\u8be2", None))
self.cpuinfo_button.setText(QCoreApplication.translate("Form", u"CPU\u4fe1\u606f\u6570\u636e\u7edf\u8ba1", None))
self.cpuinfo_result_button.setText(QCoreApplication.translate("Form", u"CPU\u4fe1\u606f\u6570\u636e\u7ed3\u679c\u67e5\u8be2", None))
# retranslateUi
功能实现逻辑代码
import time import pyecharts.charts
from pyecharts import options as opts
from util import ADB
import subprocess
import os
import sys
from PySide6.QtWidgets import QApplication, QWidget, QLabel, QPushButton, QMessageBox
from PySide6.QtCore import Slot, Signal
import performanceTools
import threading class QmyWeidge(QWidget):
#建立信号
packagenameSignal = Signal(str)
def __init__(self):
super().__init__()
self.ui = performanceTools.Ui_Form()
self.ui.setupUi(self)
self.cold_time_cal_htmlname = ""
self.cpuinfo_htmlname = ""
self.packagenameSignal.connect(self.isPackagenameExciting)#判断包名是否存在信号槽链接
def open_html_by_firefox(self, local_html_name):
# 获取当前工作目录
current_dir = os.getcwd()
# 指定本地HTML文件的路径
local_file = os.path.join(current_dir, local_html_name)
# 指定火狐浏览器的可执行文件路径
firefox_path = "C:/Program Files/Mozilla Firefox/firefox.exe" # 替换为你的火狐浏览器可执行文件的路径
# 构建火狐浏览器的命令行参数
url = 'file://' + local_file + '.html'
cmd = [firefox_path, url]
# 使用subprocess启动火狐浏览器并打开本地HTML文件
subprocess.Popen(cmd) def result_to_html(self, xaxis, yaxis, y_name, title):
pyecharts.charts.Line().add_xaxis(xaxis).add_yaxis(y_name, yaxis).set_global_opts(
title_opts=opts.TitleOpts(title=title)).render(path=f'./{title}.html') @Slot()
def on_cold_time_cal_button_clicked(self):
"""
运行10次取平均值
:param current_activity: 当前运行的app页面,或者是待测的页面
:return: 范围运行第x次的数据y
"""
#按钮置灰
self.ui.cold_time_cal_button.setEnabled(False)
self.ui.cold_time_cal_button.repaint()
self.ui.cold_time_cal_result_button.setEnabled(False)
self.ui.cold_time_cal_result_button.repaint() current_activity = ADB().get_current_activity()
x = []
y = []
for i in range(1, 11):
x.append(f"第{i}次")
cold_start_time = ADB().get_launchState_cold_totalTime(current_activity)
y.append(cold_start_time)
ADB().stop_app(current_activity)
#输出结果
self.cold_time_cal_htmlname = f"冷启动时间趋势图_平均耗时{sum(y) / 10}"
self.result_to_html(x, y ,"启动时间", self.cold_time_cal_htmlname)
#恢复按钮
self.ui.cold_time_cal_button.setEnabled(True)
self.ui.cold_time_cal_result_button.setEnabled(True) @Slot()
def on_cold_time_cal_result_button_clicked(self):
self.open_html_by_firefox(self.cold_time_cal_htmlname) @Slot()
def on_cpuinfo_button_clicked(self):
"""
运行10次取平均值
:return: 范围运行第x次的数据y
"""
#按钮置灰
self.ui.cpuinfo_button.setEnabled(False)
self.ui.cpuinfo_button.repaint()
self.ui.cpuinfo_result_button.setEnabled(False)
self.ui.cpuinfo_result_button.repaint() x = []
y = []
if self.ui.packagename.text():
for i in range(1,11):
x.append(f"第{i}次")
try:
cpu, _, _ = ADB().get_cpuinfo(self.ui.packagename.text())
y.append(round(float(cpu.replace('%', '')), 4))
time.sleep(2)
# 输出结果
self.cpuinfo_htmlname = f"cpu占比趋势图_平均占比{sum(y) / 10} %"
self.result_to_html(x, y, "cpu占比(%)", self.cpuinfo_htmlname)
except:
msgBox = QMessageBox()
msgBox.setWindowTitle("提醒")
msgBox.setText(f"{self.ui.packagename.text()}确认是否运行!未获取到对应cpu信息!")
msgBox.exec()
break
else:
self.packagenameSignal.emit(self.ui.packagename.text()) #恢复按钮
self.ui.cpuinfo_result_button.setEnabled(True)
self.ui.cpuinfo_button.setEnabled(True) @Slot()
def on_cpuinfo_result_button_clicked(self):
self.open_html_by_firefox(self.cpuinfo_htmlname) def isPackagenameExciting(self, value):
if not value :
msgBox = QMessageBox()
msgBox.setWindowTitle("提醒")
msgBox.setText("请输入包名!")
msgBox.exec() if __name__ == '__main__':
app = QApplication(sys.argv)
myWindow = QmyWeidge()
myWindow.show()
sys.exit(app.exec())
扒开源安卓性能测试工具moblieperf源码——开发属于你自己的性能稳定性测试工具的更多相关文章
- 三种web性能压力测试工具
三种web性能压力测试工具http_load webbench ab小结 题记:压力和性能测试工具很多,下文讨论的是我觉得比较容易上手,用的比较多的三种 http_load 下载地址:http://w ...
- [AapacheBench工具]web性能压力测试工具的应用与实践
背景:网站性能压力测试是性能调优过程中必不可少的一环.服务器负载太大而影响程序效率是很常见的事情,一个网站到底能够承受多大的用户访问量经常是我们最关心的问题.因此,只有让服务器处在高压情况下才能真正体 ...
- Web服务器性能/压力测试工具http_load、webbench、ab、Siege使用教程 - VPS侦探
Web服务器性能/压力测试工具http_load.webbench.ab.Siege使用教程 - VPS侦探 http://soft.vpser.net/test/http_load/http_loa ...
- Monkey Android app稳定性测试工具之Monkey使用教程
Monkey Android app稳定性测试工具之Monkey使用教程 by:授客 QQ:1033553122 由于篇幅问题,仅提供百度网盘下载链接: Android app稳定性测试工具之Monk ...
- Web服务器性能压力测试工具http_load、webbench、ab、Siege使用教程
Web服务器性能压力测试工具http_load.webbench.ab.Siege使用教程 作者: feng 日期: 2012/07/25 发表评论 (0) 查看评论 一.http_load 程序 ...
- Web服务器性能/压力测试工具http_load、webbench、ab、Siege、loadrunner
回头看看 Web服务器性能/压力测试工具http_load.webbench.ab.Siege.loadrunner
- Web性能压力测试工具——Siege
最近需要对Istio里的bookinfo进行分析,老是手去刷新太慢了,就找了个自动点的工具——Siege Siege是一款开源的压力测试工具,设计用于评估WEB应用在压力下的承受能力.可以根据配置对一 ...
- 网站服务器压力Web性能测试(1):Apache Bench:Apache自带服务器压力测试工具
一个网站或者博客到底能够承受多大的用户访问量经常是我们在用VPS或者独立服务器搭建网站了最关心的问题,还有不少人喜欢对LNMP或者LAMP进行一些优化以便提高Web性能,而优化后到底有多大的效果,就需 ...
- (总结)Web性能压力测试工具之WebBench详解
PS:在运维工作中,压力测试是一项很重要的工作.比如在一个网站上线之前,能承受多大访问量.在大访问量情况下性能怎样,这些数据指标好坏将会直接影响用户体验.但是,在压力测试中存在一个共性,那就是压力 ...
- 网站性能压力测试工具--apache ab使用详解
ab是apache自带的压力测试工具.ab非常实用,它不仅可以对apache服务器进行网站访问压力测试,也可以对其它类型的服务器进行压力测试.比如nginx.tomcat.IIS等. 下面我们开始介绍 ...
随机推荐
- 引发C++异常的常见原因(一)从报错地址到错误症状
在进行C++软件开发的过程中,会遇到很多问题,网上差不到,或者查到了也没什么信息可以用,所以这里想到了就将一些常见的问题放在一起,归纳整理一下. 本文主要的内容来源于CSDN的大佬文章:https:/ ...
- @Import 源码解析
转发请注明出处: @Import通过快速导入的方式实现把实例加入spring的IOC容器中:一般@EnableXXX注解是通过@Import实现具体的功能(@EnableXXX注解上加个@Import ...
- 【TouchGFX】屏幕概念
Screen构成 界面布局 View, 逻辑处理 Presenter Currently Active Screen 因TouchGFX分配内存的的方式是,按照最大的Screen分配,即最大View+ ...
- Linux-远程连接-ssh
- linux环境C语言实现:h264与pcm封装成AVI格式
前言 拖了很久的AVI音视频封装实例,花了一天时间终于调完了,兼容性不是太好,但作为参考学习使用应该没有问题. RIFF和AVI以及WAV格式,可以参考前面的一些文章.这里详细介绍将一个H264视 ...
- Qt5.9 UI设计(二)——最简Qt工程搭建
前言 前面一章已经介绍了QT的开发环境的安装,这里介绍一下一个最简工程的搭建 操作步骤 新建项目 选择带界面的Qt Widgets Application 设置项目位置 注意这里的目录不能有中文路径 ...
- [转帖]在 TiDB 中正确使用索引,性能提升 666 倍
https://tidb.net/book/tidb-monthly/2022/2022-04/usercase/index-666 背景 最近在给一个物流系统做TiDB POC测试,这个系统是基于 ...
- [转帖]jmeter实现分布式压测
分布式实现的前提条件: 1.master机器和奴隶机的jmeter要一致 a. jmeter版本要一致 b.jdk主要版本要一致,比如都是jdk1.8,后面的小版本不一样不影响 c.jmeter脚本中 ...
- [转帖]linux--Segfault详解
linux--Segfault详解 1 简介 1.1 段错误的定义 1.2 痛点 2 知识点 2.1 报错内容 2.2 error number 3 排除步骤(借助汇编) 3.1 日志确定错误类型 3 ...
- 银河麒麟(Ubuntu)无法上网问题的解决方法
最近部门借了几台银河麒麟的服务器. 因为有特殊用途, 不允许连接互联网,所以没办法只能搭建一个小的局域网进行处理. 但是发现在搭建过程中遇到了一些坑, 之前协助同事解决odoo问题时也遇到过, 当时本 ...