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)) |