├── README.md ├── README_EN.md ├── docker-compose-pi.yml ├── docker-compose.yml ├── grafana.py ├── iOS-perf-3x.gif ├── mysql.py ├── requirements.txt └── run.py /README.md: -------------------------------------------------------------------------------- 1 | # iOS-perf 2 | 3 | [READ IN ENGLISH](./README_EN.md) 4 | 5 | 这是一款iOS性能监控工具,支持Mac以及Windows端运行,电脑通过USB连接手机后运行脚本即可。 6 | 7 | 当前支持获取的性能数据包括GPU、CPU、内存、FPS、功耗、网络、温度,以及一系列手机硬件数据,并将根据需求继续新增。 8 | 9 | 本项目基于jlintxia开源的iOS测试方案修改而来,增加动态建表,动态增加grafana面板以及docker打包环境等特性。其中iOS性能数据来源于开源工具tidevice和py-ios-device。 10 | 11 | 12 | 注意:本项目依赖MySQL进行性能数据存储,Grafana进行数据动态展示,也就是说需要在本机或者可达的网络(比如公司局域网) 13 | 上搭建MySQL+Grafana服务,我提供了一份docker-compose.yml文件,可以使用docker快速搭建一套环境。 14 | 15 | 16 | 17 | ## 效果展示 18 | 19 | ![gif](iOS-perf-3x.gif) 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | ## 准备工作 28 | 29 | 服务端搭建依赖docker以及docker-compose,安装指南: 30 | 31 | >https://dockerdocs.cn/get-docker/ 32 | > 33 | >https://dockerdocs.cn/get-started/08_using_compose/ 34 | 35 | 运行测试依赖python3环境,安装指南: 36 | 37 | >https://www.python.org/downloads/ 38 | 39 | 40 | ### 服务端搭建 41 | 42 | 命令行运行 43 | 44 | ``` 45 | docker -v && docker-compose -v 46 | ``` 47 | 48 | 如果能正常输出版本,如下,则表示docker环境正常,可以继续 49 | 50 | >Docker version 20.10.8, build 3967b7d 51 | > 52 | >docker-compose version 1.29.2, build 5becea4c 53 | 54 | 拉取镜像并启动服务: 55 | 56 | ``` 57 | docker-compose up -d 58 | ``` 59 | **提示:初次打开`Grafana`时,系统会提示你修改密码,为了方便建议不修改,即保持账号密码均为`admin`,否则在python运行指令中将要进行对应的传参。** 60 | 61 | 62 | 63 | ### 本地环境搭建 64 | 65 | 命令行执行 66 | 67 | ``` 68 | pip install -r requirements.txt 69 | ``` 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | ## 运行命令 79 | 命令行执行: 80 | ```shell 81 | python run.py --udid=00008110-001A4D483CF2801E \ 82 | --bundleid=com.apple.Preferences \ 83 | --grafana_host=localhost \ 84 | --grafana_port=30000 \ 85 | --grafana_user=admin \ 86 | --grafana_password=admin \ 87 | --mysql_host=localhost \ 88 | --mysql_port=33306 \ 89 | --mysql_username=root \ 90 | --mysql_password=admin \ 91 | --mysql_db=iOSPerformance 92 | ``` 93 | 94 | 95 | ### 运行参数说明 96 | 97 | 98 | 99 | #### 建议修改参数 100 | 101 | >- --bundleid:待测APP的包名,通过`ideviceinstaller -l`获取,默认值为`com.apple.Preferences` 102 | >- --udid iPhone:手机的唯一标识符,通过 `idevice_id -l` 获取,客户端只连接一台手机时不用写 103 | 104 | 105 | 106 | #### Grafana可选参数 107 | 108 | > - --grafana_host:Grafana的主机地址,只写ip,不用写Scheme,也就是`http://`或者`https//`,默认值localhost 109 | > - --grafana_port:Grafana的端口号,默认值30000 110 | > - --grafana_user:Grafana的用户名,默认值admin 111 | > - --grafana_password:Grafana的密码,默认值admin 112 | 113 | 114 | 115 | #### MySQL可选参数 116 | 117 | > - --mysql_host:MySQL的主机地址,不用写Scheme,也就是`http://`或者`https//`,默认值localhost 118 | > - --mysql_port:MySQL的端口号,默认值33306 119 | > - --mysql_user:MySQL的用户名,默认值root 120 | > - --mysql_password:MySQL的密码,默认值admin 121 | 122 | 123 | 124 | ## 数据导出 125 | 126 | 命令行执行: 127 | ```shell 128 | python mysql.py --runid=iphone6_1008_1532 \ 129 | --mysql_host=localhost \ 130 | --mysql_port=33306 \ 131 | --mysql_username=root \ 132 | --mysql_password=admin \ 133 | --mysql_db=iOSPerformance 134 | ``` 135 | 136 | 其中,`--runid`为必须参数,可以从显示测试数据的Grafana页面的左上角找到,通常为手机名称+月日+时分。其余Mysql参数均为可选参数,默认值与上方[MySQL可选参数](#MySQL可选参数)相同。 137 | -------------------------------------------------------------------------------- /README_EN.md: -------------------------------------------------------------------------------- 1 | # iOS-perf 2 | 3 | This is a Performance Monitoring Tool for iOS device. OS X, Windows, Linux all support. USB or Wireless. 4 | 5 | Data type including GPU, CPU, Memery, FPS, Battery, Network, Temporary, also lots of iPhone info. 6 | 7 | This project is based on the draft of jlintxia's. Performance data is from `tidevce` and `py-ios-device`, which implement `instruments`. 8 | 9 | ATTENTION:This project use `MySQL` to save data,and use `Grafana` to show the real-time charts. YOU HAVE TO SET UP THESE TWO SERVICE. `docker-compose` in this project is the best choice to set up. 10 | 11 | 12 | 13 | ## REAL-TIME Charts 14 | 15 | ![](iOS-perf-3x.gif) 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | ## Preparation 24 | 25 | install docker and docker-compose 26 | 27 | >https://dockerdocs.cn/get-docker/ 28 | > 29 | >https://dockerdocs.cn/get-started/08_using_compose/ 30 | 31 | install python 32 | 33 | >https://www.python.org/downloads/ 34 | 35 | 36 | ### Server 37 | 38 | run in cli: 39 | 40 | ``` 41 | docker -v && docker-compose -v 42 | ``` 43 | 44 | If the outputs contain versions info means you succeed 45 | 46 | >Docker version 20.10.8, build 3967b7d 47 | > 48 | >docker-compose version 1.29.2, build 5becea4c 49 | 50 | Pull image and run: 51 | 52 | ``` 53 | docker-compose up -d 54 | ``` 55 | **Tips:first time accessing `Grafana`, you will be asked to change your default account. YOU'D BETTER IGNORE IT,keeping the username and password as `admin`,or you need to pass new username and password. ** 56 | 57 | 58 | 59 | ### Local Env 60 | 61 | run in cli: 62 | 63 | ``` 64 | pip install -r requirements.txt 65 | ``` 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | ## Command 75 | run in cli: 76 | ```shell 77 | python run.py --udid=00008110-001A4D483CF2801E \ 78 | --bundleid=com.apple.Preferences \ 79 | --grafana_host=localhost \ 80 | --grafana_port=30000 \ 81 | --grafana_user=admin \ 82 | --grafana_password=admin \ 83 | --mysql_host=localhost \ 84 | --mysql_port=33306 \ 85 | --mysql_username=root \ 86 | --mysql_password=admin \ 87 | --mysql_db=iOSPerformance 88 | ``` 89 | 90 | 91 | ### Parameters 92 | 93 | 94 | 95 | #### Target Parameters 96 | 97 | >- --bundleid:bundle id for your app,using `ideviceinstaller -l`to see all,default value is `com.apple.Preferences` 98 | >- --udid iPhone udid using `idevice_id -l` to get this. If you connect only one device, no need to pass 99 | 100 | 101 | 102 | #### Grafana Parameters 103 | 104 | > - --grafana_host:Grafana host,without Scheme,without`http://`or`https//`,default value is localhost 105 | > - --grafana_port:Grafana port,default value is 30000 106 | > - --grafana_user:Grafana username,default value is admin 107 | > - --grafana_password:Grafana password,default value is admin 108 | 109 | 110 | 111 | #### MySQL Parameters 112 | 113 | > - --mysql_host:MySQL host,without Scheme,without`http://`or`https//`,default value is localhost 114 | > - --mysql_port:MySQL port,default value is 33306 115 | > - --mysql_user:MySQL username,default value is root 116 | > - --mysql_password:MySQL password,default value is admin 117 | 118 | 119 | 120 | ## Export as Excel 121 | 122 | run in cli: 123 | ```shell 124 | python mysql.py --runid=iphone6_1008_1532 \ 125 | --mysql_host=localhost \ 126 | --mysql_port=33306 \ 127 | --mysql_username=root \ 128 | --mysql_password=admin \ 129 | --mysql_db=iOSPerformance 130 | ``` 131 | 132 | `--runid` is necessary,you can found this on the left top of `Grafana` page, or the url.,the pattern is like `PhoneName + Datetime`. 133 | 134 | -------------------------------------------------------------------------------- /docker-compose-pi.yml: -------------------------------------------------------------------------------- 1 | version: '3.5' 2 | 3 | services: 4 | grafana_cn: 5 | image: grafana/grafana:6.7.4 6 | restart: always 7 | links: 8 | - perf_database:db 9 | ports: 10 | - "30000:3000" 11 | 12 | perf_database: 13 | image: biarms/mysql:5.7 14 | environment: 15 | MYSQL_DATABASE: iOSPerformance 16 | MYSQL_ROOT_PASSWORD: admin 17 | ports: 18 | - "33006:3306" 19 | restart: always 20 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.5' 2 | 3 | services: 4 | grafana_cn: 5 | image: grafana/grafana:6.7.4 6 | restart: always 7 | links: 8 | - perf_database:db 9 | ports: 10 | - "30000:3000" 11 | 12 | perf_database: 13 | image: mysql:5.7 14 | environment: 15 | MYSQL_DATABASE: iOSPerformance 16 | MYSQL_ROOT_PASSWORD: admin 17 | ports: 18 | - "33306:3306" 19 | restart: always 20 | -------------------------------------------------------------------------------- /grafana.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | import ast 3 | import json 4 | import time 5 | import webbrowser 6 | 7 | import jsonpath 8 | import requests 9 | 10 | import tidevice 11 | from tidevice._proto import MODELS 12 | 13 | 14 | class Grafana: 15 | def __init__(self, grafana_host, grafana_port, grafana_username, grafana_password, mysql_host, mysql_port, 16 | mysql_username, mysql_password, mysql_db, run_id, device_id, bundle_id): 17 | self.grafana_host = grafana_host 18 | self.mysql_host = mysql_host 19 | self.grafana_port = grafana_port 20 | self.mysql_port = mysql_port 21 | self.grafana_username = grafana_username 22 | self.mysql_username = mysql_username 23 | self.grafana_password = grafana_password 24 | self.mysql_password = mysql_password 25 | self.mysql_db = mysql_db 26 | self.run_id = run_id 27 | self.device_id = device_id 28 | self.dashboard_url = "http://{}:{}".format(grafana_host, grafana_port) 29 | self.bundle_id = bundle_id 30 | self.add_mysql_source() 31 | 32 | def get_device_info(self, name): 33 | device = tidevice.Device(self.device_id) # iOS设备 34 | value = device.get_value() 35 | 36 | if name == "MarketName": 37 | return MODELS.get(value['ProductType']).replace(" ", "") 38 | for attr in ('DeviceName', 'ProductVersion', 'ProductType', 39 | 'ModelNumber', 'SerialNumber', 'PhoneNumber', 40 | 'CPUArchitecture', 'ProductName', 'ProtocolVersion', 41 | 'RegionInfo', 'TimeIntervalSince1970', 'TimeZone', 42 | 'UniqueDeviceID', 'WiFiAddress', 'BluetoothAddress', 43 | 'BasebandVersion'): 44 | if attr == name: 45 | if value.get(attr): 46 | return str(value.get(attr)).replace(" ", "") 47 | 48 | return None 49 | 50 | def set_anonymous(self): 51 | # useless cos this API needs grafana v8.0 + 52 | base_url = 'http://{}:{}@{}:{}/api/'.format(self.grafana_username, self.grafana_password, self.grafana_host, 53 | self.grafana_port) 54 | url = base_url + 'admin/settings' 55 | headers = { 56 | 'Accept': 'application/json', 57 | 'Content-Type': 'application/json', 58 | } 59 | body = { 60 | "updates": { 61 | "auth.anonymous": {"enabled": "true", "org_name": "Main Org.", "org_role": "Viewer"} 62 | } 63 | } 64 | response = requests.put(url=url, data=body, headers=headers) 65 | # response = requests.get(url) 66 | if response.status_code == 200: 67 | print("set anonymous success") 68 | # print("return : \n" + str(response.content.decode())) 69 | else: 70 | print("Error Code:" + str(response.status_code)) 71 | # print("Error Message: \n" + str(response.content.decode())) 72 | 73 | def get_current_panels(self, uid): 74 | # 从当前的grafana上获取panel对象 75 | # 参数为dashboaard的uid,从ui中获取 76 | headers = { 77 | 'Accept': 'application/json', 78 | 'Content-Type': 'application/json', 79 | } 80 | base_url = 'http://{}:{}@{}:{}/api/'.format(self.grafana_username, self.grafana_password, self.grafana_host, 81 | self.grafana_port) 82 | url = base_url + 'dashboards/uid/{}'.format(uid) 83 | response = requests.get(url, headers=headers) 84 | # pprint(response.text) 85 | jsonobj = json.loads(response.text) 86 | panels = jsonpath.jsonpath(jsonobj, "$..panels")[0] 87 | return panels 88 | 89 | def add_mysql_source(self): 90 | base_url = 'http://{}:{}@{}:{}/api/'.format(self.grafana_username, self.grafana_password, self.grafana_host, 91 | self.grafana_port) 92 | url = base_url + 'datasources' 93 | response = requests.get(url) 94 | if self.mysql_host == 'localhost' and self.mysql_port == '33306': 95 | db_url = 'db' 96 | else: 97 | db_url = self.mysql_host + ":" + self.mysql_port 98 | if 'mysql' not in response.content.decode(): 99 | body = { 100 | "name": "MySQL", 101 | "type": "mysql", 102 | "url": "{}".format(db_url), 103 | "password": "{}".format(self.mysql_password), 104 | "user": "{}".format(self.mysql_username), 105 | "database": "{}".format(self.mysql_db), 106 | "access": "proxy" 107 | } 108 | response = requests.post(url=url, data=body) 109 | if response.status_code == 200: 110 | print("add datasource MySQL success") 111 | else: 112 | print("Error Code:" + str(response.status_code)) 113 | print("Error Message: \n" + str(response.content.decode())) 114 | 115 | def setup_dashboard(self): 116 | 117 | panels_list = [{'cacheTimeout': None, 118 | 'content': '\n# INFO\n\nMarketName: {}' 119 | ' \n\nProductVersion: {}\n\n' 120 | 'ProductType: {}\n\nModelNumber: {}\n\nSerialNumber: {}' 121 | '\n\nPhoneNumber: {}\n\nCPUArchitecture: {}\n\nProductName: ' 122 | '{}\n\nProtocolVersion: {}\n\nRegionInfo: {}\n\nTimestamp: ' 123 | '{}\n\nTimeZone: {}\n\nUniqueDeviceID: ' 124 | '{}\n\nWiFiAddress: {}\n\n' 125 | 'BluetoothAddress: {}\n\nBasebandVersion: {}\n\nBundleID: {}\n\n\n\n'.format( 126 | self.get_device_info("MarketName"), 127 | self.get_device_info("ProductVersion"), 128 | self.get_device_info("ProductType"), 129 | self.get_device_info("ModelNumber"), 130 | self.get_device_info("SerialNumber"), 131 | self.get_device_info("PhoneNumber"), 132 | self.get_device_info("CPUArchitecture"), 133 | self.get_device_info("ProductName"), 134 | self.get_device_info("ProtocolVersion"), 135 | self.get_device_info("RegionInfo"), 136 | self.get_device_info("TimeIntervalSince1970"), 137 | self.get_device_info("TimeZone"), 138 | self.get_device_info("UniqueDeviceID"), 139 | self.get_device_info("WiFiAddress"), 140 | self.get_device_info("BluetoothAddress"), 141 | self.get_device_info("BasebandVersion"), 142 | self.bundle_id), 143 | 'datasource': 'MySQL', 'gridPos': {'h': 19, 'w': 5, 'x': 0, 'y': 0}, 'id': 10, 'links': [], 144 | 'mode': 'markdown', 'pluginVersion': '6.7.4', 'targets': [ 145 | {'format': 'time_series', 'group': [], 'metricColumn': 'none', 'rawQuery': False, 146 | 'rawSql': 'SELECT\n UNIX_TIMESTAMP() as time_sec,\n as value,\n as metric\nFROM \nWHERE $__timeFilter(time_column)\nORDER BY ASC\n', 147 | 'refId': 'A', 'select': [[{'params': ['value'], 'type': 'column'}]], 'timeColumn': 'time', 148 | 'where': [{'name': '$__timeFilter', 'params': [], 'type': 'macro'}]}], 'timeFrom': None, 149 | 'timeShift': None, 'title': 'INFO', 'type': 'text'}, 150 | {'aliasColors': {}, 'bars': False, 'dashLength': 10, 'dashes': False, 'datasource': 'MySQL', 151 | 'fill': 1, 'fillGradient': 0, 'gridPos': {'h': 5, 'w': 17, 'x': 6, 'y': 0}, 152 | 'hiddenSeries': False, 'id': 8, 153 | 'legend': {'avg': True, 'current': False, 'max': True, 'min': True, 'show': True, 154 | 'total': False, 'values': True}, 'lines': True, 'linewidth': 1, 155 | 'nullPointMode': 'null', 'options': {'dataLinks': []}, 'percentage': False, 156 | 'pointradius': 2, 'points': False, 'renderer': 'flot', 'seriesOverrides': [], 157 | 'spaceLength': 10, 'stack': False, 'steppedLine': False, 'targets': [ 158 | {'format': 'time_series', 'group': [], 'metricColumn': 'none', 'rawQuery': True, 159 | 'rawSql': 'SELECT\n $__timeGroupAlias(time,1s),\n avg(memory) AS "MEM(Mb)"\nFROM MEM\nWHERE\n $__timeFilter(time) AND\n runid = \'{}\'\nGROUP BY 1\nORDER BY $__timeGroup(time,1s)'.format(self.run_id), 160 | 'refId': 'A', 'select': [[{'params': ['value'], 'type': 'column'}]], 161 | 'timeColumn': 'time', 162 | 'where': [{'name': '$__timeFilter', 'params': [], 'type': 'macro'}]}], 'thresholds': [], 163 | 'timeFrom': None, 'timeRegions': [], 'timeShift': None, 'title': 'MEM', 164 | 'tooltip': {'shared': True, 'sort': 0, 'value_type': 'individual'}, 'type': 'graph', 165 | 'xaxis': {'buckets': None, 'mode': 'time', 'name': None, 'show': True, 'values': []}, 166 | 'yaxes': [{'format': 'short', 'label': None, 'logBase': 1, 'max': None, 'min': None, 167 | 'show': True}, 168 | {'format': 'short', 'label': None, 'logBase': 1, 'max': None, 'min': None, 169 | 'show': True}], 'yaxis': {'align': False, 'alignLevel': None}}, 170 | {'aliasColors': {}, 'bars': False, 'dashLength': 10, 'dashes': False, 'datasource': 'MySQL', 171 | 'fill': 1, 'fillGradient': 0, 'gridPos': {'h': 5, 'w': 17, 'x': 6, 'y': 5}, 172 | 'hiddenSeries': False, 'id': 4, 173 | 'legend': {'avg': True, 'current': False, 'max': True, 'min': True, 'show': True, 174 | 'total': False, 'values': True}, 'lines': True, 'linewidth': 1, 175 | 'nullPointMode': 'null', 'options': {'dataLinks': []}, 'percentage': False, 176 | 'pointradius': 2, 'points': False, 'renderer': 'flot', 'seriesOverrides': [], 177 | 'spaceLength': 10, 'stack': False, 'steppedLine': False, 'targets': [ 178 | {'format': 'time_series', 'group': [], 'metricColumn': 'none', 'rawQuery': True, 179 | 'rawSql': 'SELECT\n $__timeGroupAlias(time,1s),\n avg(use_cpu) AS "USE_CPU(%)"\nFROM CPU\nWHERE\n $__timeFilter(time) AND\n runid = \'{}\'\nGROUP BY 1\nORDER BY $__timeGroup(time,1s)'.format(self.run_id), 180 | 'refId': 'A', 'select': [[{'params': ['value'], 'type': 'column'}]], 181 | 'timeColumn': 'time', 182 | 'where': [{'name': '$__timeFilter', 'params': [], 'type': 'macro'}]}], 'thresholds': [], 183 | 'timeFrom': None, 'timeRegions': [], 'timeShift': None, 'title': 'CPU', 184 | 'tooltip': {'shared': True, 'sort': 0, 'value_type': 'individual'}, 'type': 'graph', 185 | 'xaxis': {'buckets': None, 'mode': 'time', 'name': None, 'show': True, 'values': []}, 186 | 'yaxes': [{'format': 'short', 'label': None, 'logBase': 1, 'max': None, 'min': None, 187 | 'show': True}, 188 | {'format': 'short', 'label': None, 'logBase': 1, 'max': None, 'min': None, 189 | 'show': True}], 'yaxis': {'align': False, 'alignLevel': None}}, 190 | {'aliasColors': {}, 'bars': False, 'dashLength': 10, 'dashes': False, 'datasource': 'MySQL', 191 | 'fill': 1, 'fillGradient': 0, 'gridPos': {'h': 5, 'w': 17, 'x': 6, 'y': 10}, 192 | 'hiddenSeries': False, 'id': 2, 193 | 'legend': {'avg': True, 'current': False, 'max': True, 'min': True, 'show': True, 194 | 'total': False, 'values': True}, 'lines': True, 'linewidth': 1, 195 | 'nullPointMode': 'null', 'options': {'dataLinks': []}, 'percentage': False, 196 | 'pointradius': 2, 'points': False, 'renderer': 'flot', 'seriesOverrides': [], 197 | 'spaceLength': 10, 'stack': False, 'steppedLine': False, 'targets': [ 198 | {'format': 'time_series', 'group': [{'params': ['1s', 'none'], 'type': 'time'}], 199 | 'metricColumn': 'none', 'rawQuery': True, 200 | 'rawSql': 'SELECT\n $__timeGroupAlias(time,1s),\n avg(gpu_Device) AS "GPU_Device(%)"\nFROM GPU\nWHERE\n $__timeFilter(time) AND\n runid = \'{}\'\nGROUP BY 1\nORDER BY $__timeGroup(time,1s)'.format(self.run_id), 201 | 'refId': 'A', 'select': [[{'params': ['gpu_Device'], 'type': 'column'}, 202 | {'params': ['avg'], 'type': 'aggregate'}, 203 | {'params': ['gpu_Device'], 'type': 'alias'}]], 'table': 'GPU', 204 | 'timeColumn': 'time', 'timeColumnType': 'timestamp', 205 | 'where': [{'name': '$__timeFilter', 'params': [], 'type': 'macro'}, 206 | {'datatype': 'varchar', 'name': '', 207 | 'params': ['runid', '=', "'iPhone8_1011_2245'"], 'type': 'expression'}]}, 208 | {'format': 'time_series', 'group': [], 'metricColumn': 'none', 'rawQuery': True, 209 | 'rawSql': 'SELECT\n $__timeGroupAlias(time,3s),\n avg(gpu_Renderer) AS "GPU_Renderer(%)"\nFROM GPU\nWHERE\n $__timeFilter(time) AND\n runid = \'{}\'\nGROUP BY 1\nORDER BY $__timeGroup(time,3s)'.format(self.run_id), 210 | 'refId': 'B', 'select': [[{'params': ['value'], 'type': 'column'}]], 211 | 'timeColumn': 'time', 212 | 'where': [{'name': '$__timeFilter', 'params': [], 'type': 'macro'}]}, 213 | {'format': 'time_series', 'group': [], 'metricColumn': 'none', 'rawQuery': True, 214 | 'rawSql': 'SELECT\n $__timeGroupAlias(time,3s),\n avg(gpu_Tiler) AS "GPU_Tiler(%)"\nFROM GPU\nWHERE\n $__timeFilter(time) AND\n runid = \'{}\'\nGROUP BY 1\nORDER BY $__timeGroup(time,3s)'.format(self.run_id), 215 | 'refId': 'C', 'select': [[{'params': ['value'], 'type': 'column'}]], 216 | 'timeColumn': 'time', 217 | 'where': [{'name': '$__timeFilter', 'params': [], 'type': 'macro'}]}], 'thresholds': [], 218 | 'timeFrom': None, 'timeRegions': [], 'timeShift': None, 'title': 'GPU', 219 | 'tooltip': {'shared': True, 'sort': 0, 'value_type': 'individual'}, 'type': 'graph', 220 | 'xaxis': {'buckets': None, 'mode': 'time', 'name': None, 'show': True, 'values': []}, 221 | 'yaxes': [{'format': 'short', 'label': None, 'logBase': 1, 'max': None, 'min': None, 222 | 'show': True}, 223 | {'format': 'short', 'label': None, 'logBase': 1, 'max': None, 'min': None, 224 | 'show': True}], 'yaxis': {'align': False, 'alignLevel': None}}, 225 | {'aliasColors': {}, 'bars': False, 'dashLength': 10, 'dashes': False, 'datasource': 'MySQL', 226 | 'fill': 1, 'fillGradient': 0, 'gridPos': {'h': 5, 'w': 17, 'x': 6, 'y': 15}, 227 | 'hiddenSeries': False, 'id': 6, 228 | 'legend': {'avg': True, 'current': False, 'max': True, 'min': True, 'show': True, 229 | 'total': False, 'values': True}, 'lines': True, 'linewidth': 1, 230 | 'nullPointMode': 'null', 'options': {'dataLinks': []}, 'percentage': False, 231 | 'pointradius': 2, 'points': False, 'renderer': 'flot', 'seriesOverrides': [], 232 | 'spaceLength': 10, 'stack': False, 'steppedLine': False, 'targets': [ 233 | {'format': 'time_series', 'group': [], 'metricColumn': 'none', 'rawQuery': True, 234 | 'rawSql': 'SELECT\n $__timeGroupAlias(time,1s),\n avg(fps) AS "FPS"\nFROM FPS\nWHERE\n $__timeFilter(time) AND\n runid = \'{}\'\nGROUP BY 1\nORDER BY $__timeGroup(time,1s)'.format(self.run_id), 235 | 'refId': 'A', 'select': [[{'params': ['value'], 'type': 'column'}]], 236 | 'timeColumn': 'time', 237 | 'where': [{'name': '$__timeFilter', 'params': [], 'type': 'macro'}]}], 'thresholds': [], 238 | 'timeFrom': None, 'timeRegions': [], 'timeShift': None, 'title': 'FPS', 239 | 'tooltip': {'shared': True, 'sort': 0, 'value_type': 'individual'}, 'type': 'graph', 240 | 'xaxis': {'buckets': None, 'mode': 'time', 'name': None, 'show': True, 'values': []}, 241 | 'yaxes': [{'format': 'short', 'label': None, 'logBase': 1, 'max': None, 'min': None, 242 | 'show': True}, 243 | {'format': 'short', 'label': None, 'logBase': 1, 'max': None, 'min': None, 244 | 'show': True}], 'yaxis': {'align': False, 'alignLevel': None}}, 245 | {'aliasColors': {}, 'bars': False, 'dashLength': 10, 'dashes': False, 'datasource': 'MySQL', 246 | 'fill': 1, 'fillGradient': 0, 'gridPos': {'h': 5, 'w': 17, 'x': 6, 'y': 20}, 247 | 'hiddenSeries': False, 'id': 18, 248 | 'legend': {'avg': True, 'current': False, 'max': True, 'min': True, 'show': True, 249 | 'total': False, 'values': True}, 'lines': True, 'linewidth': 1, 250 | 'nullPointMode': 'null', 'options': {'dataLinks': []}, 'percentage': False, 251 | 'pointradius': 2, 'points': False, 'renderer': 'flot', 'seriesOverrides': [], 252 | 'spaceLength': 10, 'stack': False, 'steppedLine': False, 'targets': [ 253 | {'format': 'time_series', 'group': [], 'metricColumn': 'none', 'rawQuery': True, 254 | 'rawSql': 'SELECT\n $__timeGroupAlias(time,1s),\n avg(jank) AS "JANK"\nFROM FPS\nWHERE\n $__timeFilter(time) AND\n runid = \'{}\'\nGROUP BY 1\nORDER BY $__timeGroup(time,1s)\n'.format(self.run_id), 255 | 'refId': 'A', 'select': [[{'params': ['value'], 'type': 'column'}]], 256 | 'timeColumn': 'time', 257 | 'where': [{'name': '$__timeFilter', 'params': [], 'type': 'macro'}]}, 258 | {'format': 'time_series', 'group': [], 'metricColumn': 'none', 'rawQuery': True, 259 | 'rawSql': 'SELECT\n $__timeGroupAlias(time,1s),\n avg(big_jank) AS "BIG_JANK"\nFROM FPS\nWHERE\n $__timeFilter(time) AND\n runid = \'{}\'\nGROUP BY 1\nORDER BY $__timeGroup(time,1s)'.format(self.run_id), 260 | 'refId': 'B', 'select': [[{'params': ['value'], 'type': 'column'}]], 261 | 'timeColumn': 'time', 262 | 'where': [{'name': '$__timeFilter', 'params': [], 'type': 'macro'}]}, 263 | {'format': 'time_series', 'group': [], 'metricColumn': 'none', 'rawQuery': True, 264 | 'rawSql': 'SELECT\n $__timeGroupAlias(time,1s),\n avg(stutter) AS "STUTTER"\nFROM FPS\nWHERE\n $__timeFilter(time) AND\n runid = \'{}\'\nGROUP BY 1\nORDER BY $__timeGroup(time,1s)'.format(self.run_id), 265 | 'refId': 'C', 'select': [[{'params': ['value'], 'type': 'column'}]], 266 | 'timeColumn': 'time', 267 | 'where': [{'name': '$__timeFilter', 'params': [], 'type': 'macro'}]}], 'thresholds': [], 268 | 'timeFrom': None, 'timeRegions': [], 'timeShift': None, 'title': 'JANK', 269 | 'tooltip': {'shared': True, 'sort': 0, 'value_type': 'individual'}, 'type': 'graph', 270 | 'xaxis': {'buckets': None, 'mode': 'time', 'name': None, 'show': True, 'values': []}, 271 | 'yaxes': [ 272 | {'$$hashKey': 'object:185', 'format': 'short', 'label': None, 'logBase': 1, 'max': None, 273 | 'min': None, 'show': True}, 274 | {'$$hashKey': 'object:186', 'format': 'short', 'label': None, 'logBase': 1, 'max': None, 275 | 'min': None, 'show': True}], 'yaxis': {'align': False, 'alignLevel': None}}, 276 | {'aliasColors': {}, 'bars': False, 'dashLength': 10, 'dashes': False, 'datasource': 'MySQL', 277 | 'fill': 1, 'fillGradient': 0, 'gridPos': {'h': 5, 'w': 17, 'x': 6, 'y': 25}, 278 | 'hiddenSeries': False, 'id': 14, 279 | 'legend': {'avg': True, 'current': False, 'max': True, 'min': True, 'show': True, 280 | 'total': False, 'values': True}, 'lines': True, 'linewidth': 1, 281 | 'nullPointMode': 'null', 'options': {'dataLinks': []}, 'percentage': False, 282 | 'pointradius': 2, 'points': False, 'renderer': 'flot', 'seriesOverrides': [], 283 | 'spaceLength': 10, 'stack': False, 'steppedLine': False, 'targets': [ 284 | {'format': 'time_series', 'group': [], 'metricColumn': 'none', 'rawQuery': True, 285 | 'rawSql': 'SELECT\n $__timeGroupAlias(time,1s),\n avg(temp) AS "TEMP"\nFROM TEMP\nWHERE\n $__timeFilter(time) AND\n runid = \'{}\'\nGROUP BY 1\nORDER BY $__timeGroup(time,1s)'.format(self.run_id), 286 | 'refId': 'A', 'select': [[{'params': ['temp'], 'type': 'column'}]], 'table': 'TEMP', 287 | 'timeColumn': 'time', 'timeColumnType': 'timestamp', 288 | 'where': [{'name': '$__timeFilter', 'params': [], 'type': 'macro'}]}], 'thresholds': [], 289 | 'timeFrom': None, 'timeRegions': [], 'timeShift': None, 'title': 'TEMP', 290 | 'tooltip': {'shared': True, 'sort': 0, 'value_type': 'individual'}, 'type': 'graph', 291 | 'xaxis': {'buckets': None, 'mode': 'time', 'name': None, 'show': True, 'values': []}, 292 | 'yaxes': [ 293 | {'$$hashKey': 'object:134', 'format': 'short', 'label': None, 'logBase': 1, 'max': None, 294 | 'min': None, 'show': True}, 295 | {'$$hashKey': 'object:135', 'format': 'short', 'label': None, 'logBase': 1, 'max': None, 296 | 'min': None, 'show': True}], 'yaxis': {'align': False, 'alignLevel': None}}, 297 | {'aliasColors': {}, 'bars': False, 'dashLength': 10, 'dashes': False, 'datasource': 'MySQL', 298 | 'fill': 1, 'fillGradient': 0, 'gridPos': {'h': 6, 'w': 17, 'x': 6, 'y': 30}, 299 | 'hiddenSeries': False, 'id': 16, 300 | 'legend': {'avg': True, 'current': False, 'max': True, 'min': True, 'show': True, 301 | 'total': False, 'values': True}, 'lines': True, 'linewidth': 1, 302 | 'nullPointMode': 'null', 'options': {'dataLinks': []}, 'percentage': False, 303 | 'pointradius': 2, 'points': False, 'renderer': 'flot', 'seriesOverrides': [], 304 | 'spaceLength': 10, 'stack': False, 'steppedLine': False, 'targets': [ 305 | {'format': 'time_series', 'group': [], 'metricColumn': 'none', 'rawQuery': True, 306 | 'rawSql': 'SELECT\n $__timeGroupAlias(time,1s),\n avg(gpu_cost) AS "GPU_COST"\nFROM ENG\nWHERE\n $__timeFilter(time) AND\n runid = \'{}\'\nGROUP BY 1\nORDER BY $__timeGroup(time,1s)'.format(self.run_id), 307 | 'refId': 'A', 'select': [[{'params': ['value'], 'type': 'column'}]], 308 | 'timeColumn': 'time', 309 | 'where': [{'name': '$__timeFilter', 'params': [], 'type': 'macro'}]}, 310 | {'format': 'time_series', 'group': [], 'metricColumn': 'none', 'rawQuery': True, 311 | 'rawSql': 'SELECT\n $__timeGroupAlias(time,1s),\n avg(cpu_cost) AS "CPU_COST"\nFROM ENG\nWHERE\n $__timeFilter(time) AND\n runid = \'{}\'\nGROUP BY 1\nORDER BY $__timeGroup(time,1s)'.format(self.run_id), 312 | 'refId': 'B', 'select': [[{'params': ['value'], 'type': 'column'}]], 313 | 'timeColumn': 'time', 314 | 'where': [{'name': '$__timeFilter', 'params': [], 'type': 'macro'}]}, 315 | {'format': 'time_series', 'group': [], 'metricColumn': 'none', 'rawQuery': True, 316 | 'rawSql': 'SELECT\n $__timeGroupAlias(time,1s),\n avg(network_cost) AS "NETWORK_COST"\nFROM ENG\nWHERE\n $__timeFilter(time) AND\n runid = \'{}\'\nGROUP BY 1\nORDER BY $__timeGroup(time,1s)'.format(self.run_id), 317 | 'refId': 'C', 'select': [[{'params': ['value'], 'type': 'column'}]], 318 | 'timeColumn': 'time', 319 | 'where': [{'name': '$__timeFilter', 'params': [], 'type': 'macro'}]}], 'thresholds': [], 320 | 'timeFrom': None, 'timeRegions': [], 'timeShift': None, 'title': 'BATTERY', 321 | 'tooltip': {'shared': True, 'sort': 0, 'value_type': 'individual'}, 'type': 'graph', 322 | 'xaxis': {'buckets': None, 'mode': 'time', 'name': None, 'show': True, 'values': []}, 323 | 'yaxes': [ 324 | {'$$hashKey': 'object:408', 'format': 'short', 'label': None, 'logBase': 1, 'max': None, 325 | 'min': None, 'show': True}, 326 | {'$$hashKey': 'object:409', 'format': 'short', 'label': None, 'logBase': 1, 'max': None, 327 | 'min': None, 'show': True}], 'yaxis': {'align': False, 'alignLevel': None}}, 328 | {'aliasColors': {}, 'bars': False, 'dashLength': 10, 'dashes': False, 'datasource': 'MySQL', 329 | 'fill': 1, 'fillGradient': 0, 'gridPos': {'h': 5, 'w': 17, 'x': 6, 'y': 36}, 330 | 'hiddenSeries': False, 'id': 12, 331 | 'legend': {'avg': True, 'current': False, 'max': True, 'min': True, 'show': True, 332 | 'total': False, 'values': True}, 'lines': True, 'linewidth': 1, 333 | 'nullPointMode': 'null', 'options': {'dataLinks': []}, 'percentage': False, 334 | 'pointradius': 2, 'points': False, 'renderer': 'flot', 'seriesOverrides': [], 335 | 'spaceLength': 10, 'stack': False, 'steppedLine': False, 'targets': [ 336 | {'format': 'time_series', 'group': [], 'metricColumn': 'none', 'rawQuery': True, 337 | 'rawSql': 'SELECT\n $__timeGroupAlias(time,1s),\n avg(upflow) AS "UPLOAD(Kb)"\nFROM NET\nWHERE\n $__timeFilter(time) AND\n runid = \'{}\'\nGROUP BY 1\nORDER BY $__timeGroup(time,1s)'.format(self.run_id), 338 | 'refId': 'A', 'select': [[{'params': ['upflow'], 'type': 'column'}]], 'table': 'NET', 339 | 'timeColumn': 'time', 'timeColumnType': 'timestamp', 340 | 'where': [{'name': '$__timeFilter', 'params': [], 'type': 'macro'}]}, 341 | {'format': 'time_series', 'group': [], 'metricColumn': 'none', 'rawQuery': True, 342 | 'rawSql': 'SELECT\n $__timeGroupAlias(time,1s),\n avg(downflow) AS "DOWNLOAD(Kb)"\nFROM NET\nWHERE\n $__timeFilter(time) AND\n runid = \'{}\'\nGROUP BY 1\nORDER BY $__timeGroup(time,1s)'.format(self.run_id), 343 | 'refId': 'B', 'select': [[{'params': ['value'], 'type': 'column'}]], 344 | 'timeColumn': 'time', 345 | 'where': [{'name': '$__timeFilter', 'params': [], 'type': 'macro'}]}], 'thresholds': [], 346 | 'timeFrom': None, 'timeRegions': [], 'timeShift': None, 'title': 'NET', 347 | 'tooltip': {'shared': True, 'sort': 0, 'value_type': 'individual'}, 'type': 'graph', 348 | 'xaxis': {'buckets': None, 'mode': 'time', 'name': None, 'show': True, 'values': []}, 349 | 'yaxes': [{'format': 'short', 'label': None, 'logBase': 1, 'max': None, 'min': None, 350 | 'show': True}, 351 | {'format': 'short', 'label': None, 'logBase': 1, 'max': None, 'min': None, 352 | 'show': True}], 'yaxis': {'align': False, 'alignLevel': None}}] 353 | 354 | data = ''' 355 | { 356 | "dashboard": { 357 | "id": null, 358 | "uid": null, 359 | "title": "%s", 360 | 361 | "timezone": "browser", 362 | "timepicker": { 363 | "refresh_intervals": [ 364 | "1s", 365 | "5s", 366 | "10s", 367 | "30s", 368 | "1m", 369 | "5m", 370 | "15m", 371 | "30m" 372 | ] 373 | }, 374 | "panels": %s , 375 | "templating": { 376 | "list": [] 377 | }, 378 | "annotations": { 379 | "list": [] 380 | }, 381 | "schemaVersion": 16, 382 | "version": 2 383 | }, 384 | "folderId": 0, 385 | "overwrite": true 386 | } 387 | ''' % (self.run_id, str(json.dumps(panels_list))) 388 | # pprint(data) 389 | 390 | headers = { 391 | 'Accept': 'application/json', 392 | 'Content-Type': 'application/json', 393 | } 394 | 395 | response = requests.post(url="http://{}:{}@{}:{}/api/dashboards/db".format(self.grafana_username, 396 | self.grafana_password, 397 | self.grafana_host, 398 | self.grafana_port), 399 | data=data, headers=headers) 400 | # print(response) 401 | # print(response.text) 402 | response_dict = ast.literal_eval(response.text) 403 | self.dashboard_url = "http://{}:{}".format(self.grafana_host, self.grafana_port) + response_dict['url'] + \ 404 | "?orgId=1&refresh=1s&from=now-5m&to=now" 405 | 406 | def to_explorer(self): 407 | webbrowser.open(self.dashboard_url) 408 | 409 | 410 | if __name__ == "__main__": 411 | grafana = Grafana("localhost", "30000", "admin", "admin", "localhost", "33306", 412 | "root", "admin", "iOSPerformance", "{}", 413 | "29dd01e10bb04f076c6e563c350f663d571be8ee") 414 | panels = grafana.get_current_panels("ow_UJrAnz") 415 | time.sleep(1) 416 | -------------------------------------------------------------------------------- /iOS-perf-3x.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yanbo92/iOS-perf/6687697642e665d1c20251a11b81a289f53421b1/iOS-perf-3x.gif -------------------------------------------------------------------------------- /mysql.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | import pymysql.cursors 3 | import time 4 | import xlwt 5 | import argparse 6 | 7 | 8 | class Mysql: 9 | def __init__(self, mysql_host, mysql_port, mysql_username, mysql_password, mysql_db, run_id): 10 | self.mysql_host = mysql_host 11 | self.mysql_port = mysql_port 12 | self.mysql_username = mysql_username 13 | self.mysql_password = mysql_password 14 | self.mysql_db = mysql_db 15 | self.run_id = run_id 16 | self.db_init() 17 | 18 | def db_connect(self): 19 | # 根据mysql配置参数 返回游标对象 20 | connect = pymysql.Connect( 21 | host=self.mysql_host, 22 | port=int(self.mysql_port), 23 | user=self.mysql_username, 24 | passwd=self.mysql_password, 25 | db=self.mysql_db, 26 | charset='utf8' 27 | ) 28 | # 获取连接对象 29 | return connect 30 | 31 | def db_init(self): 32 | # 33 | try: 34 | connect = self.db_connect() 35 | # 获取游标 36 | cursor = connect.cursor() 37 | cursor.execute("use {};".format(self.mysql_db)) 38 | cursor.execute("CREATE TABLE TEMP (temp VARCHAR(255), runid VARCHAR(255), " 39 | "time timestamp)") 40 | cursor.execute("CREATE TABLE ENG (gpu_cost VARCHAR(255), cpu_cost VARCHAR(255), network_cost VARCHAR(255), " 41 | "runid VARCHAR(255), time timestamp)") 42 | 43 | cursor.execute("CREATE TABLE FPS (fps VARCHAR(255), jank VARCHAR(255), big_jank VARCHAR(255), " 44 | "stutter VARCHAR(255),runid VARCHAR(255), time timestamp)") 45 | cursor.execute("CREATE TABLE GPU (gpu_Device VARCHAR(255), gpu_Renderer VARCHAR(255), " 46 | "gpu_Tiler VARCHAR(255),runid VARCHAR(255), time timestamp)") 47 | cursor.execute( 48 | "CREATE TABLE CPU (use_cpu VARCHAR(255), sys_cpu VARCHAR(255), count_cpu VARCHAR(255), " 49 | "runid VARCHAR(255), time timestamp)") 50 | cursor.execute("CREATE TABLE MEM (memory VARCHAR(255), runid VARCHAR(255), " 51 | "time timestamp)") 52 | cursor.execute("CREATE TABLE NET (upflow VARCHAR(255), downflow VARCHAR(255), runid VARCHAR(255), " 53 | "time timestamp)") 54 | 55 | time.sleep(3) 56 | connect.commit() 57 | # 关闭连接 58 | cursor.close() 59 | connect.close() 60 | print("db init success") 61 | except BaseException as e: 62 | print("Error Info: " + str(e)) 63 | print("Maybe this database dont need initializing, just ignore the error") 64 | 65 | def insert_cpu(self, value): 66 | cpu_sql_prefix = "INSERT INTO CPU (use_cpu,runid) VALUES('" 67 | sql = cpu_sql_prefix + value + "','" + self.run_id + "')" 68 | connect = self.db_connect() 69 | cursor = connect.cursor() 70 | cursor.execute(sql) 71 | connect.commit() 72 | # 关闭连接 73 | cursor.close() 74 | connect.close() 75 | 76 | def insert_memory(self, value): 77 | mem_sql_prefix = "INSERT INTO MEM (memory,runid) VALUES('" 78 | sql = mem_sql_prefix + value + "','" + self.run_id + "')" 79 | connect = self.db_connect() 80 | cursor = connect.cursor() 81 | cursor.execute(sql) 82 | connect.commit() 83 | # 关闭连接 84 | cursor.close() 85 | connect.close() 86 | 87 | def insert_fps(self, fps, jank=0, big_jank=0, stutter=0): 88 | # fps, jank, big_jank, stutter 89 | fps_sql_prefix = "INSERT INTO FPS (fps,jank,big_jank,stutter,runid) VALUES('" 90 | sql = fps_sql_prefix + str(fps) + "','" + str(jank) + "','" + str(big_jank) + "','" + str(stutter) + "','" \ 91 | + self.run_id + "')" 92 | print("**************fps:" +sql) 93 | connect = self.db_connect() 94 | cursor = connect.cursor() 95 | cursor.execute(sql) 96 | connect.commit() 97 | # 关闭连接 98 | cursor.close() 99 | connect.close() 100 | 101 | def insert_gpu(self, gpu_device, gpu_renderer, gpu_tiler): 102 | gpu_sql_prefix = "INSERT INTO GPU (gpu_Device,gpu_Renderer,gpu_Tiler,runid) VALUES('" 103 | sql = gpu_sql_prefix + str(gpu_device) + "','" + str(gpu_renderer) + "','" + str(gpu_tiler) + \ 104 | "','" + self.run_id + "')" 105 | connect = self.db_connect() 106 | cursor = connect.cursor() 107 | cursor.execute(sql) 108 | connect.commit() 109 | # 关闭连接 110 | cursor.close() 111 | connect.close() 112 | 113 | def insert_net(self, upflow, downflow): 114 | net_sql_prefix = "INSERT INTO NET (upflow,downflow,runid) VALUES('" 115 | sql = net_sql_prefix + str(upflow) + "','" + str(downflow) + "','" + self.run_id + "')" 116 | connect = self.db_connect() 117 | cursor = connect.cursor() 118 | cursor.execute(sql) 119 | connect.commit() 120 | # 关闭连接 121 | cursor.close() 122 | connect.close() 123 | 124 | def insert_temp(self, temp): 125 | tmp_sql_prefix = "INSERT INTO TEMP (temp,runid) VALUES('" 126 | sql = tmp_sql_prefix + str(temp) + "','" + self.run_id + "')" 127 | connect = self.db_connect() 128 | cursor = connect.cursor() 129 | cursor.execute(sql) 130 | connect.commit() 131 | # 关闭连接 132 | cursor.close() 133 | connect.close() 134 | 135 | def insert_eng(self, gpu_cost, cpu_cost, network_cost): 136 | eng_sql_prefix = "INSERT INTO ENG (gpu_cost,cpu_cost,network_cost,runid) VALUES('" 137 | sql = eng_sql_prefix + str(gpu_cost) + "','" + str(cpu_cost) + "','" + str(network_cost) + "','" + \ 138 | self.run_id + "')" 139 | connect = self.db_connect() 140 | cursor = connect.cursor() 141 | cursor.execute(sql) 142 | connect.commit() 143 | # 关闭连接 144 | cursor.close() 145 | connect.close() 146 | 147 | def export(self): 148 | table_list = ["FPS", "GPU", "CPU", "MEM", "NET", "ENG", "TEMP"] 149 | workbook = xlwt.Workbook() 150 | for t in table_list: 151 | connect = self.db_connect() 152 | cursor = connect.cursor() 153 | 154 | cursor.execute('select * from ' + t + ' WHERE runid = \'{}\''.format(self.run_id)) 155 | # 重置游标的位置 156 | cursor.scroll(0, mode='absolute') 157 | # 搜取所有结果 158 | results = cursor.fetchall() 159 | 160 | # 获取MYSQL里面的数据字段名称 161 | fields = cursor.description 162 | sheet = workbook.add_sheet('table_' + '{}'.format(t), cell_overwrite_ok=True) 163 | 164 | # 写上字段信息 165 | for field in range(0, len(fields)): 166 | sheet.write(0, field, fields[field][0]) 167 | 168 | # 获取并写入数据段信息 169 | row = 1 170 | col = 0 171 | for row in range(1, len(results) + 1): 172 | for col in range(0, len(fields)): 173 | sheet.write(row, col, u'%s' % results[row - 1][col]) 174 | 175 | connect.commit() 176 | # 关闭连接 177 | cursor.close() 178 | connect.close() 179 | 180 | workbook.save("{}.xls".format(self.run_id)) 181 | print("export finish, check file: " + "{}.xls".format(self.run_id)) 182 | 183 | 184 | if __name__ == "__main__": 185 | parser = argparse.ArgumentParser() 186 | 187 | parser.add_argument("--mysql_host", type=str, required=False, default="10.0.43.163") 188 | parser.add_argument("--mysql_port", type=str, required=False, default="33306") 189 | parser.add_argument("--mysql_username", type=str, required=False, default="root") 190 | parser.add_argument("--mysql_password", type=str, required=False, default="admin") 191 | parser.add_argument("--mysql_db", type=str, required=False, default="iOSPerformance") 192 | parser.add_argument("--runid", type=str, required=True) 193 | 194 | args = parser.parse_args() 195 | print("Parameters list:") 196 | for arg in vars(args): 197 | print(arg, getattr(args, arg)) 198 | 199 | mysql_host = args.mysql_host 200 | mysql_port = args.mysql_port 201 | mysql_username = args.mysql_username 202 | mysql_password = args.mysql_password 203 | mysql_db = args.mysql_db 204 | run_id = args.runid 205 | 206 | mysql = Mysql(mysql_host, mysql_port, mysql_username, mysql_password, mysql_db, run_id) 207 | mysql.export() 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | tidevice==0.7.14 2 | PyMySQL==1.0.2 3 | jsonpath==0.82 4 | xlwt==1.3.0 5 | requests==2.27.1 6 | numpy==1.22.4 7 | py-ios-device==2.2.3.5 8 | pyinstaller -------------------------------------------------------------------------------- /run.py: -------------------------------------------------------------------------------- 1 | # -*- coding: UTF-8 -*- 2 | """ 3 | @Author : liyanbo 4 | 5 | """ 6 | import datetime 7 | import time 8 | import argparse 9 | import threading 10 | from grafana import Grafana 11 | from mysql import Mysql 12 | import tidevice 13 | from tidevice._usbmux import Usbmux 14 | from tidevice._proto import MODELS 15 | from tidevice._perf import DataType 16 | from ios_device.servers.Instrument import InstrumentServer 17 | from ios_device import py_ios_device 18 | 19 | 20 | def get_energy(rpc, pid): 21 | rpc._start() 22 | channel = "com.apple.xcode.debug-gauge-data-providers.Energy" 23 | attr = {} 24 | print("start", rpc.call(channel, "startSamplingForPIDs:", {pid}).selector) 25 | while True: 26 | ret = rpc.call(channel, "sampleAttributes:forPIDs:", attr, {pid}) 27 | # print(ret.selector) 28 | if 'energy.gpu.cost' in ret.selector[pid].keys(): 29 | print("gpu:", ret.selector[pid]['energy.gpu.cost']) 30 | gpu_cost = ret.selector[pid]['energy.gpu.cost'] 31 | else: 32 | gpu_cost = '0' 33 | 34 | if 'energy.cpu.cost' in ret.selector[pid].keys(): 35 | print("cpu:", ret.selector[pid]['energy.cpu.cost']) 36 | cpu_cost = ret.selector[pid]['energy.cpu.cost'] 37 | else: 38 | cpu_cost = '0' 39 | 40 | if 'energy.networking.cost' in ret.selector[pid].keys(): 41 | print("networking:", ret.selector[pid]['energy.networking.cost']) 42 | network_cost = ret.selector[pid]['energy.networking.cost'] 43 | else: 44 | network_cost = '0' 45 | 46 | mysql.insert_eng(gpu_cost, cpu_cost, network_cost) 47 | time.sleep(1) 48 | 49 | 50 | def get_fps(rpc): 51 | def callback_fps(res): 52 | print('FPS打印', res) 53 | # fps数据 54 | ss = str(res) 55 | fps_test = ss.split("'FPS':")[1].split(".")[0] 56 | jank_test = ss.split("'jank':")[1].split(",")[0] 57 | big_jank = ss.split("'big_jank':")[1].split(",")[0] 58 | stutter = ss.split("'stutter':")[1][0:5].split("}")[0] 59 | mysql.insert_fps(fps_test, jank_test, big_jank, stutter) 60 | 61 | py_ios_device.start_get_fps(rpc_channel=rpc, callback=callback_fps) 62 | 63 | 64 | def get_temp(td): 65 | while True: 66 | io_power = (td.get_io_power()) 67 | diagnostics = io_power['Diagnostics'] 68 | io_registry = diagnostics['IORegistry'] 69 | temperature = io_registry['Temperature'] 70 | temp_float = str(float(temperature) / 100) 71 | print("temp:", temp_float) 72 | mysql.insert_temp(temp_float) 73 | time.sleep(5) 74 | 75 | 76 | def start_test(): 77 | 78 | def get_pid(bid): 79 | t = tidevice.Device(device_id) 80 | app_infos = list(t.instruments.app_list()) 81 | for app in app_infos: 82 | plugin = [] 83 | if app['CFBundleIdentifier'] == bid: 84 | print('app',app) 85 | plugin = [app] 86 | if plugin.count == 1: 87 | pid = 0 88 | ps = t.instruments.app_process_list(plugin) 89 | for p in ps: 90 | pid = p['pid'] 91 | if pid: 92 | return pid 93 | return None 94 | 95 | t = tidevice.Device(device_id) # iOS设备 96 | try: 97 | pid = t.app_start(app_bundle_id) 98 | except BaseException as e: 99 | pid = get_pid(app_bundle_id) 100 | rpc = InstrumentServer(udid=device_id, network=True).init() 101 | 102 | t_energy = threading.Thread(target=get_energy, args=(rpc, pid)) 103 | t_fps = threading.Thread(target=get_fps, args=[rpc]) 104 | t_temp = threading.Thread(target=get_temp, args=[t]) 105 | 106 | perf = tidevice.Performance(t, [DataType.CPU, DataType.MEMORY, DataType.NETWORK, DataType.FPS, DataType.PAGE, 107 | DataType.GPU, DataType.SCREENSHOT]) 108 | 109 | def callback(_type: tidevice.DataType, value: dict): 110 | print(_type, value) 111 | if _type.value == "cpu": 112 | print('CPU打印', value) 113 | ss = str(value) # 转成str 114 | use_cpu = ss.split("'value':")[1][0:6].split("}")[0] 115 | # 数据存数据库连接数据库 116 | mysql.insert_cpu(use_cpu) 117 | if _type.value == "memory": 118 | print('内存打印', value) 119 | ss = str(value) 120 | memory = ss.split("'value':")[1][0:6].split("}")[0] 121 | # 数据存数据库连接数据库 122 | mysql.insert_memory(memory) 123 | if _type.value == "network": 124 | print('网络打印', value) 125 | downFlow = value['downFlow'] 126 | upFlow = value['upFlow'] 127 | mysql.insert_net(upFlow, downFlow) 128 | # if _type.value == "fps": 129 | # print('tidevice.fps打印', value) 130 | # fps = value['fps'] 131 | # mysql.insert_fps(fps) 132 | if _type.value == "gpu": 133 | print('GPU', value) 134 | device = value['device'] 135 | renderer = value['renderer'] 136 | tiler = value['tiler'] 137 | mysql.insert_gpu(device, renderer, tiler) 138 | 139 | try: 140 | perf.start(app_bundle_id, callback=callback) 141 | except BaseException as ssl_error: 142 | print(ssl_error) 143 | time.sleep(5) 144 | perf.start(app_bundle_id, callback=callback) 145 | 146 | t_temp.start() 147 | t_energy.start() 148 | t_fps.start() 149 | time.sleep(99999) # 测试时长 150 | perf.stop() 151 | 152 | 153 | def get_device_info(name): 154 | device = tidevice.Device(device_id) # iOS设备 155 | value = device.get_value() 156 | 157 | for attr in ('DeviceName', 'ProductVersion', 'ProductType', 158 | 'ModelNumber', 'SerialNumber', 'PhoneNumber', 159 | 'CPUArchitecture', 'ProductName', 'ProtocolVersion', 160 | 'RegionInfo', 'TimeIntervalSince1970', 'TimeZone', 161 | 'UniqueDeviceID', 'WiFiAddress', 'BluetoothAddress', 162 | 'BasebandVersion'): 163 | if attr == name: 164 | if value.get(attr): 165 | return str(value.get(attr)).replace(" ", "") 166 | if name == "MarketName": 167 | return MODELS.get(value['ProductType']).replace(" ", "") 168 | return None 169 | 170 | 171 | if __name__ == "__main__": 172 | 173 | # 参数处理部分 174 | parser = argparse.ArgumentParser() 175 | parser.add_argument("--udid", type=str, required=False, default="") 176 | parser.add_argument("--bundleid", type=str, required=False, default="com.apple.Preferences") 177 | # parser.add_argument("--grafana_host", type=str, required=False, default="10.0.43.163") 178 | # parser.add_argument("--mysql_host", type=str, required=False, default="10.0.43.163") 179 | parser.add_argument("--grafana_host", type=str, required=False, default="localhost") 180 | parser.add_argument("--mysql_host", type=str, required=False, default="localhost") 181 | parser.add_argument("--grafana_port", type=str, required=False, default="30000") 182 | parser.add_argument("--mysql_port", type=str, required=False, default="33306") 183 | parser.add_argument("--grafana_username", type=str, required=False, default="admin") 184 | parser.add_argument("--mysql_username", type=str, required=False, default="root") 185 | parser.add_argument("--grafana_password", type=str, required=False, default="admin") 186 | parser.add_argument("--mysql_password", type=str, required=False, default="admin") 187 | parser.add_argument("--mysql_db", type=str, required=False, default="iOSPerformance") 188 | parser.add_argument("--runid", type=str, required=False, default="") 189 | parser.add_argument('--export', type=int, required=False, default=0, help='python run.py --export=1 --runid=iphone6_1012_1111') 190 | 191 | args = parser.parse_args() 192 | print("Parameters list:") 193 | for arg in vars(args): 194 | print(arg, getattr(args, arg)) 195 | 196 | app_bundle_id = args.bundleid # 测试iOS应用包名 197 | device_id = args.udid # 测试设备ID 198 | grafana_host = args.grafana_host 199 | mysql_host = args.mysql_host 200 | grafana_port = args.grafana_port 201 | mysql_port = args.mysql_port 202 | grafana_username = args.grafana_username 203 | mysql_username = args.mysql_username 204 | grafana_password = args.grafana_password 205 | mysql_password = args.mysql_password 206 | mysql_db = args.mysql_db 207 | run_id = args.runid 208 | export = args.export 209 | 210 | # 运行代码 211 | if not device_id: 212 | um = Usbmux() 213 | for d in um.device_list(): 214 | if d.conn_type == ' USB': 215 | device_id = d.udid 216 | 217 | if not export: 218 | tidevice_obj = tidevice.Device(device_id) # iOS设备 219 | 220 | MarketName = get_device_info("MarketName") 221 | run_id = get_device_info("MarketName") + "_" + datetime.datetime.now().strftime("%m%d_%H%M") 222 | 223 | mysql = Mysql(mysql_host, mysql_port, mysql_username, mysql_password, mysql_db, run_id) 224 | 225 | grafana = Grafana(grafana_host, grafana_port, grafana_username, grafana_password, mysql_host, mysql_port, 226 | mysql_username, mysql_password, mysql_db, run_id, device_id, app_bundle_id) 227 | grafana.setup_dashboard() 228 | grafana.to_explorer() 229 | 230 | start_test() 231 | else: 232 | mysql = Mysql(mysql_host, mysql_port, mysql_username, mysql_password, mysql_db, run_id) 233 | mysql.export() 234 | 235 | 236 | --------------------------------------------------------------------------------