michael@0: # Copyright (c) 2012 The Chromium Authors. All rights reserved. michael@0: # Use of this source code is governed by a BSD-style license that can be michael@0: # found in the LICENSE file. michael@0: michael@0: """Utilities for iotop/top style profiling for android.""" michael@0: michael@0: import collections michael@0: import json michael@0: import os michael@0: import subprocess michael@0: import sys michael@0: import urllib michael@0: michael@0: import constants michael@0: import io_stats_parser michael@0: michael@0: michael@0: class DeviceStatsMonitor(object): michael@0: """Class for collecting device stats such as IO/CPU usage. michael@0: michael@0: Args: michael@0: adb: Instance of AndroidComannds. michael@0: hz: Frequency at which to sample device stats. michael@0: """ michael@0: michael@0: DEVICE_PATH = constants.TEST_EXECUTABLE_DIR + '/device_stats_monitor' michael@0: PROFILE_PATH = (constants.DEVICE_PERF_OUTPUT_DIR + michael@0: '/device_stats_monitor.profile') michael@0: RESULT_VIEWER_PATH = os.path.abspath(os.path.join( michael@0: os.path.dirname(os.path.realpath(__file__)), 'device_stats_monitor.html')) michael@0: michael@0: def __init__(self, adb, hz, build_type): michael@0: self._adb = adb michael@0: host_path = os.path.abspath(os.path.join( michael@0: constants.CHROME_DIR, 'out', build_type, 'device_stats_monitor')) michael@0: self._adb.PushIfNeeded(host_path, DeviceStatsMonitor.DEVICE_PATH) michael@0: self._hz = hz michael@0: michael@0: def Start(self): michael@0: """Starts device stats monitor on the device.""" michael@0: self._adb.SetFileContents(DeviceStatsMonitor.PROFILE_PATH, '') michael@0: self._process = subprocess.Popen( michael@0: ['adb', 'shell', '%s --hz=%d %s' % ( michael@0: DeviceStatsMonitor.DEVICE_PATH, self._hz, michael@0: DeviceStatsMonitor.PROFILE_PATH)]) michael@0: michael@0: def StopAndCollect(self, output_path): michael@0: """Stops monitoring and saves results. michael@0: michael@0: Args: michael@0: output_path: Path to save results. michael@0: michael@0: Returns: michael@0: String of URL to load results in browser. michael@0: """ michael@0: assert self._process michael@0: self._adb.KillAll(DeviceStatsMonitor.DEVICE_PATH) michael@0: self._process.wait() michael@0: profile = self._adb.GetFileContents(DeviceStatsMonitor.PROFILE_PATH) michael@0: michael@0: results = collections.defaultdict(list) michael@0: last_io_stats = None michael@0: last_cpu_stats = None michael@0: for line in profile: michael@0: if ' mmcblk0 ' in line: michael@0: stats = io_stats_parser.ParseIoStatsLine(line) michael@0: if last_io_stats: michael@0: results['sectors_read'].append(stats.num_sectors_read - michael@0: last_io_stats.num_sectors_read) michael@0: results['sectors_written'].append(stats.num_sectors_written - michael@0: last_io_stats.num_sectors_written) michael@0: last_io_stats = stats michael@0: elif line.startswith('cpu '): michael@0: stats = self._ParseCpuStatsLine(line) michael@0: if last_cpu_stats: michael@0: results['user'].append(stats.user - last_cpu_stats.user) michael@0: results['nice'].append(stats.nice - last_cpu_stats.nice) michael@0: results['system'].append(stats.system - last_cpu_stats.system) michael@0: results['idle'].append(stats.idle - last_cpu_stats.idle) michael@0: results['iowait'].append(stats.iowait - last_cpu_stats.iowait) michael@0: results['irq'].append(stats.irq - last_cpu_stats.irq) michael@0: results['softirq'].append(stats.softirq- last_cpu_stats.softirq) michael@0: last_cpu_stats = stats michael@0: units = { michael@0: 'sectors_read': 'sectors', michael@0: 'sectors_written': 'sectors', michael@0: 'user': 'jiffies', michael@0: 'nice': 'jiffies', michael@0: 'system': 'jiffies', michael@0: 'idle': 'jiffies', michael@0: 'iowait': 'jiffies', michael@0: 'irq': 'jiffies', michael@0: 'softirq': 'jiffies', michael@0: } michael@0: with open(output_path, 'w') as f: michael@0: f.write('display(%d, %s, %s);' % (self._hz, json.dumps(results), units)) michael@0: return 'file://%s?results=file://%s' % ( michael@0: DeviceStatsMonitor.RESULT_VIEWER_PATH, urllib.quote(output_path)) michael@0: michael@0: michael@0: @staticmethod michael@0: def _ParseCpuStatsLine(line): michael@0: """Parses a line of cpu stats into a CpuStats named tuple.""" michael@0: # Field definitions: http://www.linuxhowtos.org/System/procstat.htm michael@0: cpu_stats = collections.namedtuple('CpuStats', michael@0: ['device', michael@0: 'user', michael@0: 'nice', michael@0: 'system', michael@0: 'idle', michael@0: 'iowait', michael@0: 'irq', michael@0: 'softirq', michael@0: ]) michael@0: fields = line.split() michael@0: return cpu_stats._make([fields[0]] + [int(f) for f in fields[1:8]])