1 #!/usr/bin/env python 2 # Copyright 2015 The Chromium Authors. All rights reserved. 3 # Use of this source code is governed by a BSD-style license that can be 4 # found in the LICENSE file. 5 6 """Launches a daemon to monitor android device temperatures & status. 7 8 This script will repeatedly poll the given devices for their temperatures and 9 status every 60 seconds and dump the stats to file on the host. 10 """ 11 12 import argparse 13 import collections 14 import json 15 import logging 16 import logging.handlers 17 import os 18 import re 19 import socket 20 import sys 21 import time 22 23 if __name__ == '__main__': 24 sys.path.append( 25 os.path.abspath(os.path.join(os.path.dirname(__file__), 26 '..', '..', '..'))) 27 28 from devil.android import battery_utils 29 from devil.android import device_blacklist 30 from devil.android import device_errors 31 from devil.android import device_utils 32 from devil.android.tools import script_common 33 34 35 # Various names of sensors used to measure cpu temp 36 CPU_TEMP_SENSORS = [ 37 # most nexus devices 38 'tsens_tz_sensor0', 39 # android one 40 'mtktscpu', 41 # nexus 9 42 'CPU-therm', 43 ] 44 45 DEVICE_FILE_VERSION = 1 46 DEVICE_FILE = os.path.join( 47 os.path.expanduser('~'), '.android', 48 '%s__android_device_status.json' % socket.gethostname().split('.')[0]) 49 50 MEM_INFO_REGEX = re.compile(r'.*?\:\s*(\d+)\s*kB') # ex: 'MemTotal: 185735 kB' 51 52 53 def get_device_status_unsafe(device): 54 """Polls the given device for various info. 55 56 Returns: A dict of the following format: 57 { 58 'battery': { 59 'level': 100, 60 'temperature': 123 61 }, 62 'build': { 63 'build.id': 'ABC12D', 64 'product.device': 'chickenofthesea' 65 }, 66 'imei': 123456789, 67 'mem': { 68 'avail': 1000000, 69 'total': 1234567, 70 }, 71 'processes': 123, 72 'state': 'good', 73 'temp': { 74 'some_sensor': 30 75 }, 76 'uptime': 1234.56, 77 } 78 """ 79 status = collections.defaultdict(dict) 80 81 # Battery 82 battery = battery_utils.BatteryUtils(device) 83 battery_info = battery.GetBatteryInfo() 84 try: 85 level = int(battery_info.get('level')) 86 except (KeyError, TypeError, ValueError): 87 level = None 88 if level and level >= 0 and level <= 100: 89 status['battery']['level'] = level 90 try: 91 temperature = int(battery_info.get('temperature')) 92 except (KeyError, TypeError, ValueError): 93 temperature = None 94 if temperature: 95 status['battery']['temperature'] = temperature 96 97 # Build 98 status['build']['build.id'] = device.build_id 99 status['build']['product.device'] = device.build_product 100 101 # Memory 102 mem_info = '' 103 try: 104 mem_info = device.ReadFile('/proc/meminfo') 105 except device_errors.AdbShellCommandFailedError: 106 logging.exception('Unable to read /proc/meminfo') 107 for line in mem_info.splitlines(): 108 match = MEM_INFO_REGEX.match(line) 109 if match: 110 try: 111 value = int(match.group(1)) 112 except ValueError: 113 continue 114 key = line.split(':')[0].strip() 115 if key == 'MemTotal': 116 status['mem']['total'] = value 117 elif key == 'MemFree': 118 status['mem']['free'] = value 119 120 # Process 121 try: 122 status['processes'] = len(device.ListProcesses()) 123 except device_errors.AdbCommandFailedError: 124 logging.exception('Unable to count process list.') 125 126 # CPU Temps 127 # Find a thermal sensor that matches one in CPU_TEMP_SENSORS and read its 128 # temperature. 129 files = [] 130 try: 131 files = device.RunShellCommand( 132 'grep -lE "%s" /sys/class/thermal/thermal_zone*/type' % '|'.join( 133 CPU_TEMP_SENSORS), shell=True, check_return=True) 134 except device_errors.AdbShellCommandFailedError: 135 logging.exception('Unable to list thermal sensors.') 136 for f in files: 137 try: 138 sensor_name = device.ReadFile(f).strip() 139 temp = float(device.ReadFile(f[:-4] + 'temp').strip()) # s/type^/temp 140 status['temp'][sensor_name] = temp 141 except (device_errors.AdbShellCommandFailedError, ValueError): 142 logging.exception('Unable to read thermal sensor %s', f) 143 144 # Uptime 145 try: 146 uptimes = device.ReadFile('/proc/uptime').split() 147 status['uptime'] = float(uptimes[0]) # Take the first field (actual uptime) 148 except (device_errors.AdbShellCommandFailedError, ValueError): 149 logging.exception('Unable to read /proc/uptime') 150 151 try: 152 status['imei'] = device.GetIMEI() 153 except device_errors.CommandFailedError: 154 logging.exception('Unable to read IMEI') 155 status['imei'] = 'unknown' 156 157 status['state'] = 'available' 158 return status 159 160 161 def get_device_status(device): 162 try: 163 status = get_device_status_unsafe(device) 164 except device_errors.DeviceUnreachableError: 165 status = collections.defaultdict(dict) 166 status['state'] = 'offline' 167 return status 168 169 170 def get_all_status(blacklist): 171 status_dict = { 172 'version': DEVICE_FILE_VERSION, 173 'devices': {}, 174 } 175 176 healthy_devices = device_utils.DeviceUtils.HealthyDevices(blacklist) 177 parallel_devices = device_utils.DeviceUtils.parallel(healthy_devices) 178 results = parallel_devices.pMap(get_device_status).pGet(None) 179 180 status_dict['devices'] = { 181 device.serial: result for device, result in zip(healthy_devices, results) 182 } 183 184 if blacklist: 185 for device, reason in blacklist.Read().iteritems(): 186 status_dict['devices'][device] = { 187 'state': reason.get('reason', 'blacklisted')} 188 189 status_dict['timestamp'] = time.time() 190 return status_dict 191 192 193 def main(argv): 194 """Launches the device monitor. 195 196 Polls the devices for their battery and cpu temperatures and scans the 197 blacklist file every 60 seconds and dumps the data to DEVICE_FILE. 198 """ 199 200 parser = argparse.ArgumentParser( 201 description='Launches the device monitor.') 202 script_common.AddEnvironmentArguments(parser) 203 parser.add_argument('--blacklist-file', help='Path to device blacklist file.') 204 args = parser.parse_args(argv) 205 206 logger = logging.getLogger() 207 logger.setLevel(logging.DEBUG) 208 handler = logging.handlers.RotatingFileHandler( 209 '/tmp/device_monitor.log', maxBytes=10 * 1024 * 1024, backupCount=5) 210 fmt = logging.Formatter('%(asctime)s %(levelname)s %(message)s', 211 datefmt='%y%m%d %H:%M:%S') 212 handler.setFormatter(fmt) 213 logger.addHandler(handler) 214 script_common.InitializeEnvironment(args) 215 216 blacklist = (device_blacklist.Blacklist(args.blacklist_file) 217 if args.blacklist_file else None) 218 219 logging.info('Device monitor running with pid %d, adb: %s, blacklist: %s', 220 os.getpid(), args.adb_path, args.blacklist_file) 221 while True: 222 start = time.time() 223 status_dict = get_all_status(blacklist) 224 with open(DEVICE_FILE, 'wb') as f: 225 json.dump(status_dict, f, indent=2, sort_keys=True) 226 logging.info('Got status of all devices in %.2fs.', time.time() - start) 227 time.sleep(60) 228 229 230 if __name__ == '__main__': 231 sys.exit(main(sys.argv[1:])) 232