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 '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