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