Home | History | Annotate | Download | only in tools
      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