1# Copyright (c) 2012 The Chromium Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5"""Utilities for iotop/top style profiling for android.""" 6 7import collections 8import json 9import os 10import subprocess 11import sys 12import urllib 13 14import constants 15import io_stats_parser 16 17 18class DeviceStatsMonitor(object): 19 """Class for collecting device stats such as IO/CPU usage. 20 21 Args: 22 adb: Instance of AndroidComannds. 23 hz: Frequency at which to sample device stats. 24 """ 25 26 DEVICE_PATH = constants.TEST_EXECUTABLE_DIR + '/device_stats_monitor' 27 PROFILE_PATH = (constants.DEVICE_PERF_OUTPUT_DIR + 28 '/device_stats_monitor.profile') 29 RESULT_VIEWER_PATH = os.path.abspath(os.path.join( 30 os.path.dirname(os.path.realpath(__file__)), 'device_stats_monitor.html')) 31 32 def __init__(self, adb, hz, build_type): 33 self._adb = adb 34 host_path = os.path.abspath(os.path.join( 35 constants.CHROME_DIR, 'out', build_type, 'device_stats_monitor')) 36 self._adb.PushIfNeeded(host_path, DeviceStatsMonitor.DEVICE_PATH) 37 self._hz = hz 38 39 def Start(self): 40 """Starts device stats monitor on the device.""" 41 self._adb.SetFileContents(DeviceStatsMonitor.PROFILE_PATH, '') 42 self._process = subprocess.Popen( 43 ['adb', 'shell', '%s --hz=%d %s' % ( 44 DeviceStatsMonitor.DEVICE_PATH, self._hz, 45 DeviceStatsMonitor.PROFILE_PATH)]) 46 47 def StopAndCollect(self, output_path): 48 """Stops monitoring and saves results. 49 50 Args: 51 output_path: Path to save results. 52 53 Returns: 54 String of URL to load results in browser. 55 """ 56 assert self._process 57 self._adb.KillAll(DeviceStatsMonitor.DEVICE_PATH) 58 self._process.wait() 59 profile = self._adb.GetFileContents(DeviceStatsMonitor.PROFILE_PATH) 60 61 results = collections.defaultdict(list) 62 last_io_stats = None 63 last_cpu_stats = None 64 for line in profile: 65 if ' mmcblk0 ' in line: 66 stats = io_stats_parser.ParseIoStatsLine(line) 67 if last_io_stats: 68 results['sectors_read'].append(stats.num_sectors_read - 69 last_io_stats.num_sectors_read) 70 results['sectors_written'].append(stats.num_sectors_written - 71 last_io_stats.num_sectors_written) 72 last_io_stats = stats 73 elif line.startswith('cpu '): 74 stats = self._ParseCpuStatsLine(line) 75 if last_cpu_stats: 76 results['user'].append(stats.user - last_cpu_stats.user) 77 results['nice'].append(stats.nice - last_cpu_stats.nice) 78 results['system'].append(stats.system - last_cpu_stats.system) 79 results['idle'].append(stats.idle - last_cpu_stats.idle) 80 results['iowait'].append(stats.iowait - last_cpu_stats.iowait) 81 results['irq'].append(stats.irq - last_cpu_stats.irq) 82 results['softirq'].append(stats.softirq- last_cpu_stats.softirq) 83 last_cpu_stats = stats 84 units = { 85 'sectors_read': 'sectors', 86 'sectors_written': 'sectors', 87 'user': 'jiffies', 88 'nice': 'jiffies', 89 'system': 'jiffies', 90 'idle': 'jiffies', 91 'iowait': 'jiffies', 92 'irq': 'jiffies', 93 'softirq': 'jiffies', 94 } 95 with open(output_path, 'w') as f: 96 f.write('display(%d, %s, %s);' % (self._hz, json.dumps(results), units)) 97 return 'file://%s?results=file://%s' % ( 98 DeviceStatsMonitor.RESULT_VIEWER_PATH, urllib.quote(output_path)) 99 100 101 @staticmethod 102 def _ParseCpuStatsLine(line): 103 """Parses a line of cpu stats into a CpuStats named tuple.""" 104 # Field definitions: http://www.linuxhowtos.org/System/procstat.htm 105 cpu_stats = collections.namedtuple('CpuStats', 106 ['device', 107 'user', 108 'nice', 109 'system', 110 'idle', 111 'iowait', 112 'irq', 113 'softirq', 114 ]) 115 fields = line.split() 116 return cpu_stats._make([fields[0]] + [int(f) for f in fields[1:8]]) 117