├── LICENSE ├── README.md ├── _main_.py ├── info.py ├── info_task.py ├── requirements.txt ├── task.py └── utils.py /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Charming 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 写在前面 2 | 本文主要介绍一个基于 **uiautomator2** 封装的一个 **Python** 库 **android-catcher**,该库的功能主要有对 **Android** 设备进行 **UI 自动化测试**和**采集手机性能数据**,适用于如列表滑动、录制视频等各种测试场景下 **CPU、内存、帧率**等信息的捕获,方便后续分析。 3 | 4 | 5 | # 安装 6 | ### 安装 Python 7 | 自动化测试的脚本是用 **Python 3** 写的,要运行脚本需要先安装 Python 3 环境 8 | 下载地址: 9 | [Python 3.6.5](https://www.python.org/ftp/python/3.6.5/python-3.6.5.exe) 10 | ### 安装 android-catcher 依赖 11 | 打开脚本目录执行以下命令,安装依赖 12 | ``` 13 | pip install -r requirements.txt 14 | ``` 15 | 16 | 17 | # Usage 18 | ### uiautomator2 的使用方式 19 | 安装完 uiautomator2 之后,一般只需要执行以下命令对设备进行初始化,在设备上安装 uiautomator2 服务 20 | 21 | ``` 22 | python -m uiautomator2 init 23 | ``` 24 | 25 | 出现以下提示则表示安装成功 26 | ![uiautomator初始化成功](https://img-blog.csdn.net/20180424213848579?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0NoYXJtaW5nV29uZw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) 27 | 更多的 uiautomator2 的使用方式可参考:https://github.com/openatx/uiautomator2 28 | 29 | ### 脚本文件说明 30 | 这个脚本库根目录下主要的文件有 31 | 32 | - **info.py**:手机性能信息采集的脚本,其中定义了父类 Info,已实现的子类有 **CPUInfo(CPU信息)、MemInfo(内存信息)、FPSInfo(帧率信息)、NetInfo(网络流量信息)**,使用者可以从 Info 派生子类来实现自己的采集需求 33 | - **task.py**:测试场景的脚本,其中定义了父类 Task,因为没有固定的测试场景,因此使用者需要从 Task 派生子类并重写 `Task#execute` 方法来自定义的测试场景,自定义方式可参考:https://github.com/openatx/uiautomator2 34 | - **info_task.py**:测试场景和采集信息灵活结合的脚本,使用者不需要用到 35 | - **utils.py**:工具方法脚本 36 | - **\_main\_.py**:任务运行的入口脚本,当没有具体的测试场景,只是想采集指定时间段的信息,直接运行该脚本 37 | 38 | ### 参数说明 39 | - -s:必选参数,指定设备号,可通过 `adb devices` 获取 40 | - -a:必选参数,要测试进程的 applicationId 41 | - -f:可选参数,采样间隔,单位为秒,不建议设置太短,最好是大于 0.1s,默认是 1s 42 | - -d:可选参数,采样持续时间,默认为10s 43 | - -i:可选参数,需要采集的信息,可以设置多个,目前可选的有四个,分别为 `cpu、mem、fps、net`,用 "," 隔开,如 `-i cpu,mem,fps,net` 44 | - -o:可选参数,采集到的信息的输出目录,如 "." 表示当前脚本所在的目录,默认为 "." 45 | 46 | ### 生成文件说明 47 | 采集到的信息根据信息类型分别存放在指定输出目录的 `cpu_stats、mem_stats、fps_stats、net_stats` 四个子目录下,文件名为 `信息类型_设备号_applicationId_版本号_测试场景名_时间戳`,如 `cpu_d3c2edaa_video.like_RecordVideo_1.9.9_1524122928.csv.csv`,实际效果大致如下图 48 | ![](https://img-blog.csdn.net/20180424214359452?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0NoYXJtaW5nV29uZw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) 49 | 50 | 51 | 输出文件为 csv 文件,直接打开和用 Excel 打开的效果分别如下图 52 | ![这里写图片描述](https://img-blog.csdn.net/20180424214415833?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0NoYXJtaW5nV29uZw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) 53 | ![这里写图片描述](https://img-blog.csdn.net/20180424214422913?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0NoYXJtaW5nV29uZw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) 54 | 另外可以为测试的每个阶段添加一个节点说明 55 | 56 | ``` 57 | task.period = "idle" 58 | ``` 59 | 生成类似如下的图 60 | ![这里写图片描述](https://img-blog.csdn.net/20180424220059723?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0NoYXJtaW5nV29uZw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) 61 | ### 无自定义测试场景的使用方式 62 | 适用于没有具体测试场景,在脚本运行之后一段时间内都处于采集状态的情况,持续时间可以通过配置参数指定,过程中使用者可以随意操作手机。通过命令行直接运行 `_main_.py` 脚本文件,并指定相关参数 63 | 比方说我要采集 applicationId 为 `video.like` 这个应用 10s 内的 cpu 信息和内存信息,采样间隔为 200ms,输出目录为当前目录,那么可以在脚本所在的目录执行以下命令 64 | 65 | ``` 66 | python _main_.py -s 设备号-a video.like -f 0.2 -d 10 -i mem,cpu -o . 67 | ``` 68 | 69 | 脚本运行结束之后可以在根目录下看到如下图所示的文件生成 70 | 71 | ![这里写图片描述](https://img-blog.csdn.net/20180424214443810?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0NoYXJtaW5nV29uZw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) 72 | 73 | 注:要带 -d 参数,指定采集的持续时间,否则脚本默认运行 10s,并且无需 -t 参数,默认测试场景名为 `Random` 74 | 75 | ### 自定义测试场景的使用方式 76 | 自定义测试场景不能直接调用 `_main_.py` 脚本,需要创建新的脚本,继承 `task.py#Task` 并重写 `Task#execute` 方法,在 `Task#execute` 中实现自定义测试场景的逻辑,如下图所示: 77 | 78 | ![这里写图片描述](https://img-blog.csdn.net/20180424214454377?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0NoYXJtaW5nV29uZw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) 79 | 80 | 这里创建了名为 `start_app.py` 的脚本,运行命令: 81 | 82 | ``` 83 | python start_app.py -s 设备号-a 进程名 -f 0.1 -i cpu,mem -o . 84 | ``` 85 | 86 | 就可以启动对应的 APP,并采集 CPU 信息和内存信息,采样间隔为 100ms,输出到当前目录。注意这里没有了 -d 参数,因为采集的持续时间以测试任务的持续时间的持续时间为准,设置的参数一定要按照说明来,否则不能采集到数据 87 | 如果想采集自定义的信息,可以继承 `info.py#Info` 并重写 `Info#get_start_info` 和 `Info#get_end_info` 方法,可参考已实现的四种信息采集的写法,最后通过 `Task#add_info` 方法添加。 88 | 89 | 自定义好测试场景之后,调用 `_main_#main` 方法,传入测试场景实例,测试场景的名称会作为输出文件命名的一部分,这里最好取能准确表达测试场景的名称,如某个 APP 录制视频测试场景的名称为 `RecordVideo` 90 | 采集到的信息可通过 Excel 制成图表,以下是完整录制视频这个测试场景的 CPU 占比和内存的变化 91 | ![这里写图片描述](https://img-blog.csdn.net/20180424214747362?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0NoYXJtaW5nV29uZw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) 92 | ![这里写图片描述](https://img-blog.csdn.net/20180424214854715?watermark/2/text/aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L0NoYXJtaW5nV29uZw==/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70) 93 | 通过图表可以直观分析应用不同版本和不同场景下的性能状况 94 | 95 | # 写在最后 96 | 以上就是该库的一些使用介绍。由于工作经验尚浅,Python 也是现学现用,在写这个库时,可能会有许多考虑不周或不完善的地方,有能力的小伙伴可以直接修改该库,以实现更多自定义功能,另外也希望大家能多用,多发现问题,欢迎 issue,欢迎 star,有新的使用需求和想法也欢迎提出,后续会不断完善,感谢! 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /_main_.py: -------------------------------------------------------------------------------- 1 | import getopt 2 | import re 3 | import sys 4 | 5 | import uiautomator2 as u2 6 | 7 | import utils 8 | from info import CPUInfo, MemInfo, FPSInfo, NetInfo 9 | from info_task import InfoTask 10 | from task import RandomTask 11 | 12 | 13 | # 命令行配置参数 14 | # -s device 设备号 15 | # -a applicationid 包名 16 | # -f frequence 频率,即采样间隔,单位为秒 17 | # -d duration 无自定义测试场景时,为RandomTask指定运行时间 18 | # -i info 需要采集的信息 19 | # -o output 输出目录,默认为"." 20 | # -u user 账号 21 | # -p password 密码 22 | # -l url 需要安装app的url地址 23 | 24 | def main(task): 25 | device = None 26 | applicationid = None 27 | interval = 1 28 | duration = 10 29 | info_names = [] 30 | output = "." 31 | opts, args = getopt.getopt(sys.argv[1:], "s:a:f:d:i:o:t:u:p:l:") 32 | 33 | for opt, value in opts: 34 | if opt == "-s": 35 | device = value 36 | elif opt == "-a": 37 | applicationid = value 38 | elif opt == "-f": 39 | interval = float(value) 40 | elif opt == "-d": 41 | duration = float(value) 42 | elif opt == "-i": 43 | info_names = re.split("\s?,\s?", value) 44 | elif opt == "-o": 45 | if value is not None: 46 | output = value 47 | 48 | d = u2.connect(device) 49 | d.app_start(applicationid) 50 | pid = utils.get_pid_by_applicationid(d, applicationid) 51 | version_name = utils.get_version_name_by_applicationid(d, applicationid) 52 | 53 | task.d = d 54 | task.device = device 55 | task.applicationid = applicationid 56 | task.version_name = version_name 57 | task.pid = pid 58 | task.interval = interval 59 | task.output = output 60 | 61 | for info in info_names: 62 | if info == "cpu": 63 | task.add_info(CPUInfo()) 64 | elif info == "mem": 65 | task.add_info(MemInfo()) 66 | elif info == "fps": 67 | task.add_info(FPSInfo()) 68 | elif info == "net": 69 | task.add_info(NetInfo()) 70 | 71 | if isinstance(task, RandomTask): 72 | task.duration = duration 73 | 74 | info_task = InfoTask(task) 75 | info_task.start() 76 | 77 | 78 | if __name__ == '__main__': 79 | main(RandomTask("Random")) 80 | -------------------------------------------------------------------------------- /info.py: -------------------------------------------------------------------------------- 1 | import re 2 | import threading 3 | import time 4 | 5 | import utils 6 | 7 | 8 | class Info(object): 9 | 10 | def __init__(self): 11 | self.task = None 12 | 13 | def get_start_info(self): 14 | pass 15 | 16 | def get_end_info(self): 17 | pass 18 | 19 | def get_index(self): 20 | if self.task.period == "" or self.task.period is None: 21 | return time.strftime("%H:%M:%S") 22 | else: 23 | return self.task.period 24 | 25 | 26 | class CPUInfo(Info): 27 | TIME = "time" 28 | CPU_RATE = "进程CPU占比(%)" 29 | JIFFIES = "时间片" 30 | 31 | def __init__(self): 32 | super().__init__() 33 | self.count = 1 34 | self.is_running = True 35 | 36 | def get_cpu_usage(self): 37 | cpu_usage = 0.0 38 | result = self.get_cpu_action() 39 | for i in range(2, len(result)): 40 | cpu_usage += float(result[i]) 41 | return cpu_usage 42 | 43 | def get_cpu_action(self): 44 | cpu_path = "/proc/stat" 45 | info = self.task.d.adb_shell("cat " + cpu_path) 46 | return re.split("\s+", info.split("\n")[0]) 47 | 48 | def get_process_cpu_usage(self): 49 | result = self.get_process_cpu_action() 50 | cpu_usage = float(result[1]) + float(result[2]) 51 | return cpu_usage 52 | 53 | def get_process_cpu_action(self): 54 | cpu_path = "/proc/" + self.task.pid + "/stat" 55 | info = self.task.d.adb_shell("cat " + cpu_path) 56 | result = re.split("\s+", info) 57 | return [result[1], result[13], result[14]] 58 | 59 | def get_start_info(self): 60 | t = threading.Thread(target=self.get_cpu_info) 61 | t.start() 62 | 63 | def get_end_info(self): 64 | self.is_running = False 65 | 66 | def get_cpu_info(self): 67 | dirs = self.task.output + "/cpu_stats/" 68 | file_name = "cpu_" + self.task.device + "_" + self.task.applicationid + "_" + self.task.version_name + "_" + self.task.name 69 | field_names = [self.TIME, self.CPU_RATE, self.JIFFIES] 70 | writer = utils.get_csv_writer(dirs, file_name, field_names) 71 | while self.is_running: 72 | start_all_cpu = self.get_cpu_usage() 73 | start_p_cpu = self.get_process_cpu_usage() 74 | time.sleep(self.task.interval) 75 | end_all_cpu = self.get_cpu_usage() 76 | end_p_cpu = self.get_process_cpu_usage() 77 | cpu_rate = 0.0 78 | if (end_all_cpu - start_all_cpu) != 0: 79 | cpu_rate = (end_p_cpu - start_p_cpu) * 100.00 / ( 80 | end_all_cpu - start_all_cpu) 81 | if cpu_rate < 0: 82 | cpu_rate = 0 83 | elif cpu_rate > 100: 84 | cpu_rate = 100 85 | jiffies = start_p_cpu 86 | writer.writerow( 87 | {self.TIME: self.get_index(), 88 | self.CPU_RATE: format(cpu_rate, ".2f"), self.JIFFIES: jiffies}) 89 | self.count += 1 90 | 91 | 92 | class MemInfo(Info): 93 | TIME = "time" 94 | NATIVE_HEAP = "Native Heap(MB)" 95 | DALVIK_HEAP = "Dalvik Heap(MB)" 96 | 97 | def __init__(self): 98 | super().__init__() 99 | self.count = 1 100 | self.is_running = True 101 | 102 | def get_start_info(self): 103 | t = threading.Thread(target=self.get_mem_info) 104 | t.start() 105 | 106 | def get_end_info(self): 107 | self.is_running = False 108 | 109 | def get_mem_info(self): 110 | dirs = self.task.output + "/mem_stats/" 111 | file_name = "mem_" + self.task.device + "_" + self.task.applicationid + "_" + self.task.version_name + "_" + self.task.name 112 | field_names = [self.TIME, self.NATIVE_HEAP, self.DALVIK_HEAP] 113 | writer = utils.get_csv_writer(dirs, file_name, field_names) 114 | while self.is_running: 115 | native_info = self.task.d.adb_shell("dumpsys meminfo " + self.task.pid + " | grep 'Native Heap '") 116 | native_pss = format(int(re.findall(r"\d+", native_info)[0]) / 1000.0, ".2f") 117 | dalvik_info = self.task.d.adb_shell("dumpsys meminfo " + self.task.pid + " | grep 'Dalvik Heap '") 118 | dalvik_pss = format(int(re.findall(r"\d+", dalvik_info)[0]) / 1000.0, ".2f") 119 | writer.writerow( 120 | {self.TIME: self.get_index(), 121 | self.NATIVE_HEAP: native_pss, self.DALVIK_HEAP: dalvik_pss}) 122 | self.count += 1 123 | time.sleep(self.task.interval) 124 | 125 | 126 | class FPSInfo(Info): 127 | TIME = "time" 128 | FPS = "FPS" 129 | 130 | def __init__(self): 131 | super().__init__() 132 | self.is_running = True 133 | self.is_first = True 134 | self.last_time = 0.0 135 | self.last_fps = 0 136 | self.count = 1 137 | 138 | def get_start_info(self): 139 | t = threading.Thread(target=self.get_fps_info) 140 | t.start() 141 | 142 | def get_end_info(self): 143 | self.is_running = False 144 | 145 | def get_fps_info(self): 146 | command = "dumpsys gfxinfo " + self.task.pid + " | grep 'Total frames'" 147 | dirs = self.task.output + "/fps_stats/" 148 | file_name = "fps_" + self.task.device + "_" + self.task.applicationid + "_" + self.task.version_name + "_" + self.task.name 149 | field_names = [self.TIME, self.FPS] 150 | writer = utils.get_csv_writer(dirs, file_name, field_names) 151 | while self.is_running: 152 | if self.is_first: 153 | self.last_time = time.time_ns() 154 | self.last_fps = int(re.findall("\d+", self.task.d.adb_shell(command))[0]) 155 | self.is_first = False 156 | 157 | current_time = time.time_ns() 158 | current_fps = int(re.findall("\d+", self.task.d.adb_shell(command))[0]) 159 | time_delta = (current_time - self.last_time) / 1000000000.0 160 | fps_delta = current_fps - self.last_fps 161 | fps = fps_delta / time_delta 162 | self.last_time = current_time 163 | self.last_fps = current_fps 164 | writer.writerow({self.TIME: self.get_index(), self.FPS: "{:.2f}".format(fps)}) 165 | self.count += 1 166 | time.sleep(self.task.interval) 167 | 168 | 169 | class NetInfo(Info): 170 | TIME = "time" 171 | DOWN_SPEED = "下载速度(KB/s)" 172 | UP_SPEED = "上传速度(KB/s)" 173 | AVERAGE_DOWN_SPEED = "平均下载速度(KB/s)" 174 | AVERAGEUP_SPEED = "平均上传速度(KB/s)" 175 | TOTAL_DOWN_SPEED = "下载总流量(KB)" 176 | TOTAL_UP_SPEED = "上传总流量(KB)" 177 | 178 | def __init__(self): 179 | super().__init__() 180 | self.count = 1 181 | self.is_running = True 182 | self.is_first = True 183 | self.last_time = 0 184 | self.last_net_up = 0 185 | self.last_net_down = 0 186 | self.start_time = 0 187 | self.start_net_up = 0 188 | self.start_net_down = 0 189 | 190 | def get_start_info(self): 191 | t = threading.Thread(target=self.get_net_info) 192 | t.start() 193 | 194 | def get_end_info(self): 195 | self.is_running = False 196 | 197 | def get_net_info(self): 198 | command = "cat /proc/" + self.task.pid + "/net/dev | grep wlan" 199 | dirs = self.task.output + "/net_stats/" 200 | file_name = "net_" + self.task.device + "_" + self.task.applicationid + "_" + self.task.version_name + "_" + self.task.name 201 | field_names = [self.TIME, 202 | self.DOWN_SPEED, 203 | self.UP_SPEED, 204 | self.AVERAGE_DOWN_SPEED, 205 | self.AVERAGEUP_SPEED, 206 | self.TOTAL_DOWN_SPEED, 207 | self.TOTAL_UP_SPEED] 208 | writer = utils.get_csv_writer(dirs, file_name, field_names) 209 | while self.is_running: 210 | if self.is_first: 211 | self.start_time = time.time_ns() 212 | self.last_time = self.start_time 213 | net_info = self.task.d.adb_shell(command) 214 | net_array = re.split("\s+", net_info) 215 | self.start_net_down = int(net_array[2]) 216 | self.last_net_down = self.start_net_down 217 | self.start_net_up = int(net_array[10]) 218 | self.last_net_up = self.start_net_up 219 | self.is_first = False 220 | current_time = time.time_ns() 221 | current_info = self.task.d.adb_shell(command) 222 | current_array = re.split("\s+", current_info) 223 | current_net_down = int(current_array[2]) 224 | current_net_up = int(current_array[10]) 225 | time_delta = (current_time - self.last_time) / 1000000000.0 226 | time_total = (current_time - self.start_time) / 1000000000.0 227 | net_delta_up = (current_net_up - self.last_net_up) / 1024.0 228 | net_delta_down = (current_net_down - self.last_net_down) / 1024.0 229 | net_total_up = (current_net_up - self.start_net_up) / 1024.0 230 | net_total_down = (current_net_down - self.start_net_down) / 1024.0 231 | net_speed_up = net_delta_up / time_delta 232 | net_speed_down = net_delta_down / time_delta 233 | net_average_speed_up = net_total_up / time_total 234 | net_average_speed_down = net_total_down / time_total 235 | 236 | writer.writerow({self.TIME: self.get_index(), 237 | self.DOWN_SPEED: "{:.2f}".format(net_speed_down), 238 | self.UP_SPEED: "{:.2f}".format(net_speed_up), 239 | self.AVERAGE_DOWN_SPEED: "{:.2f}".format(net_average_speed_down), 240 | self.AVERAGEUP_SPEED: "{:.2f}".format(net_average_speed_up), 241 | self.TOTAL_DOWN_SPEED: "{:.2f}".format(net_total_down), 242 | self.TOTAL_UP_SPEED: "{:.2f}".format(net_total_up) 243 | }) 244 | # print("下载速度:{:.2f} KB/s".format(net_speed_down)) 245 | # print("上传速度:{:.2f} KB/s".format(net_speed_up)) 246 | # print("平均下载速度:{:.2f} KB/s".format(net_average_speed_down)) 247 | # print("平均上传速度:{:.2f} KB/s".format(net_average_speed_up)) 248 | # print("下载总流量:{:.0f} KB/s".format(net_total_down)) 249 | # print("上传总流量:{:.0f} KB/s".format(net_total_up)) 250 | # print("\n") 251 | self.last_time = current_time 252 | self.last_net_up = current_net_up 253 | self.last_net_down = current_net_down 254 | self.count += 1 255 | time.sleep(self.task.interval) 256 | -------------------------------------------------------------------------------- /info_task.py: -------------------------------------------------------------------------------- 1 | class InfoTask(object): 2 | 3 | def __init__(self, task): 4 | self.task = task 5 | 6 | def start(self): 7 | for info in self.task.info_list: 8 | info.get_start_info() 9 | 10 | if self.task is not None: 11 | self.task.execute() 12 | 13 | for info in self.task.info_list: 14 | info.get_end_info() 15 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | certifi==2018.1.18 2 | chardet==3.0.4 3 | decorator==4.2.1 4 | fire==0.1.3 5 | futures==3.0.5 6 | humanize==0.5.1 7 | idna==2.6 8 | Pillow==5.1.0 9 | progress==1.3 10 | py==1.5.3 11 | requests==2.18.4 12 | retry==0.9.2 13 | six==1.11.0 14 | tornado==5.0.2 15 | uiautomator2==0.1.1.dev32 16 | urllib3==1.22 17 | weditor==0.0.4.dev8 18 | -------------------------------------------------------------------------------- /task.py: -------------------------------------------------------------------------------- 1 | import time 2 | 3 | 4 | class Task(object): 5 | def __init__(self, name): 6 | self.name = name 7 | self.period = None 8 | self.d = None 9 | self.device = None 10 | self.applicationid = None 11 | self.version_name = None 12 | self.pid = None 13 | self.interval = None 14 | self.output = None 15 | self.info_list = set([]) 16 | 17 | def execute(self): 18 | pass 19 | 20 | def add_info(self, info): 21 | self.info_list.add(info) 22 | info.task = self 23 | 24 | def set_device(self, d): 25 | self.d = d 26 | 27 | 28 | class RandomTask(Task): 29 | 30 | def __init__(self, name): 31 | super().__init__(name) 32 | self.duration = 0.0 33 | 34 | def execute(self): 35 | time.sleep(self.duration) 36 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | import csv 2 | import os 3 | import re 4 | import time 5 | 6 | 7 | def get_csv_writer(dirs, file_name, field_names): 8 | if not os.path.exists(dirs): 9 | os.makedirs(dirs) 10 | file_path = dirs + file_name + "_" + format(time.time(), ".0f") + ".csv" 11 | mem_csv = open(file_path, 'w', newline='', encoding="GBK") 12 | writer = csv.DictWriter(mem_csv, fieldnames=field_names) 13 | writer.writeheader() 14 | return writer 15 | 16 | 17 | def get_applicationid_by_pid(d, pid): 18 | ps_info = re.findall("\S+", d.adb_shell("ps | grep " + pid)) 19 | return ps_info[len(ps_info) - 1] 20 | 21 | 22 | def get_pid_by_applicationid(d, applicationid): 23 | ps_info = re.findall("\S+", d.adb_shell("ps | grep " + applicationid + "$")) 24 | return ps_info[1] 25 | 26 | 27 | def get_version_name_by_applicationid(d, applicationid): 28 | version_info = d.adb_shell("dumpsys package " + applicationid + " | grep versionName") 29 | return re.findall("\d+.+\d", version_info)[0] 30 | --------------------------------------------------------------------------------