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 'MemTotal' == key: 116 status['mem']['total'] = value 117 elif 'MemFree' == key: 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 = {'state': 'offline'} 166 return status 167 168 169 def get_all_status(blacklist): 170 status_dict = { 171 'version': DEVICE_FILE_VERSION, 172 'devices': {}, 173 } 174 175 healthy_devices = device_utils.DeviceUtils.HealthyDevices(blacklist) 176 parallel_devices = device_utils.DeviceUtils.parallel(healthy_devices) 177 results = parallel_devices.pMap(get_device_status).pGet(None) 178 179 status_dict['devices'] = { 180 device.serial: result for device, result in zip(healthy_devices, results) 181 } 182 183 if blacklist: 184 for device, reason in blacklist.Read().iteritems(): 185 status_dict['devices'][device] = { 186 'state': reason.get('reason', 'blacklisted')} 187 188 status_dict['timestamp'] = time.time() 189 return status_dict 190 191 192 def main(argv): 193 """Launches the device monitor. 194 195 Polls the devices for their battery and cpu temperatures and scans the 196 blacklist file every 60 seconds and dumps the data to DEVICE_FILE. 197 """ 198 199 parser = argparse.ArgumentParser( 200 description='Launches the device monitor.') 201 script_common.AddEnvironmentArguments(parser) 202 parser.add_argument('--blacklist-file', help='Path to device blacklist file.') 203 args = parser.parse_args(argv) 204 205 logger = logging.getLogger() 206 logger.setLevel(logging.DEBUG) 207 handler = logging.handlers.RotatingFileHandler( 208 '/tmp/device_monitor.log', maxBytes=10 * 1024 * 1024, backupCount=5) 209 fmt = logging.Formatter('%(asctime)s %(levelname)s %(message)s', 210 datefmt='%y%m%d %H:%M:%S') 211 handler.setFormatter(fmt) 212 logger.addHandler(handler) 213 script_common.InitializeEnvironment(args) 214 215 blacklist = (device_blacklist.Blacklist(args.blacklist_file) 216 if args.blacklist_file else None) 217 218 logging.info('Device monitor running with pid %d, adb: %s, blacklist: %s', 219 os.getpid(), args.adb_path, args.blacklist_file) 220 while True: 221 start = time.time() 222 status_dict = get_all_status(blacklist) 223 with open(DEVICE_FILE, 'wb') as f: 224 json.dump(status_dict, f, indent=2, sort_keys=True) 225 logging.info('Got status of all devices in %.2fs.', time.time() - start) 226 time.sleep(60) 227 228 229 if __name__ == '__main__': 230 sys.exit(main(sys.argv[1:])) 231