Chromium Code Reviews| Index: tools/android/activity_monitor/activity_monitor.py |
| diff --git a/tools/android/activity_monitor/activity_monitor.py b/tools/android/activity_monitor/activity_monitor.py |
| new file mode 100755 |
| index 0000000000000000000000000000000000000000..2b45c151a4817116234b77b3813bab676511f68b |
| --- /dev/null |
| +++ b/tools/android/activity_monitor/activity_monitor.py |
| @@ -0,0 +1,147 @@ |
| +#!/usr/bin/env 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. |
| + |
| +"""Provides iotop/top style profiling for android. |
| + |
| +Usage: |
| + ./activity_monitor.py --hz=20 --duration=5 --outfile=/tmp/foo |
| +""" |
| + |
| +import collections |
| +import json |
| +import optparse |
| +import os |
| +import subprocess |
| +import sys |
| +import time |
| +import urllib |
| + |
| +sys.path.append(os.path.join(sys.path[0], '..', '..', '..', 'build','android')) |
| +from pylib import android_commands |
|
bulach
2012/07/16 17:52:26
nit: while at it,
from pylib import constants
|
| +from pylib import io_stats_parser |
| + |
| + |
| +ACTIVITY_MONITOR_DEVICE_PATH = '/data/local/tmp/activity_monitor' |
| +ACTIVITY_MONITOR_HOST_PATH = os.path.abspath(os.path.join( |
| + os.path.dirname(os.path.realpath(__file__)), '..', '..', '..', |
|
bulach
2012/07/16 17:52:26
nit: pylib.constants.CHROME_DIR
|
| + 'out', 'Release', 'activity_monitor')) |
| +PROFILE_PATH = '/sdcard/Download/activity_monitor.profile' |
| +RESULT_VIEWER_PATH = os.path.abspath(os.path.join( |
| + os.path.dirname(os.path.realpath(__file__)), 'activity_monitor.html')) |
| + |
| + |
| +class ActivityMonitor(object): |
| + |
| + def __init__(self, adb=None, hz=20): |
|
bulach
2012/07/16 17:52:26
I just noticed that this is used by build/android/
|
| + """Create an ActivityMonitor |
| + |
| + Args |
| + adb: Instance of AndroidComannds. |
| + hz: Frequency at which to sample activity. |
| + """ |
| + self._adb = adb or android_commands.AndroidCommands() |
| + self._adb.PushIfNeeded(ACTIVITY_MONITOR_HOST_PATH, |
| + ACTIVITY_MONITOR_DEVICE_PATH) |
| + self._hz = hz |
| + |
| + def Start(self): |
| + """Starts ActivityMonitor on the device.""" |
| + self._adb.SetFileContents(PROFILE_PATH, '') |
| + self._process = subprocess.Popen( |
| + ['adb', 'shell', '%s --hz=%d %s' % ( |
| + ACTIVITY_MONITOR_DEVICE_PATH, self._hz, 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(ACTIVITY_MONITOR_DEVICE_PATH) |
| + self._process.wait() |
| + profile = self._adb.GetFileContents(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' % (RESULT_VIEWER_PATH, |
| + urllib.quote(output_path)) |
| + |
| + |
| + @staticmethod |
| + def ParseCpuStatsLine(line): |
|
bulach
2012/07/16 17:52:26
nit: add a _ prefix
|
| + """Parses a line of cpu stats into a CpuStats named tuple.""" |
| + # Field definitions: http://www.linuxhowtos.org/System/procstat.htm |
| + CpuStats = collections.namedtuple('CpuStats', |
|
bulach
2012/07/16 17:52:26
nit: cpu_stats
|
| + ['device', |
| + 'user', |
| + 'nice', |
| + 'system', |
| + 'idle', |
| + 'iowait', |
| + 'irq', |
| + 'softirq', |
| + ]) |
| + fields = line.split() |
| + return CpuStats._make([fields[0]] + [int(f) for f in fields[1:8]]) |
| + |
| + |
| +def main(argv): |
| + option_parser = optparse.OptionParser() |
| + option_parser.add_option('--hz', type='int', default=20, |
| + help='Number of samples/sec.') |
| + option_parser.add_option('--duration', type='int', default=5, |
| + help='Seconds to monitor.') |
| + option_parser.add_option('--outfile', default='/tmp/activitymonitor', |
| + help='Location to start output file.') |
| + options, args = option_parser.parse_args(argv) |
| + |
| + activity_monitor = ActivityMonitor(hz=options.hz) |
| + activity_monitor.Start() |
| + print 'Waiting for %d seconds while profiling.' % options.duration |
| + time.sleep(options.duration) |
| + url = activity_monitor.StopAndCollect(options.outfile) |
| + print 'View results in browser at %s' % url |
| + |
| +if __name__ == '__main__': |
| + sys.exit(main(sys.argv)) |