117 lines
4.2 KiB
Python
117 lines
4.2 KiB
Python
# 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]])
|