OLD | NEW |
---|---|
(Empty) | |
1 #!/usr/bin/env python | |
2 # | |
3 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
4 # Use of this source code is governed by a BSD-style license that can be | |
5 # found in the LICENSE file. | |
6 | |
7 """Provides iotop/top style profiling for android. | |
8 | |
9 Usage: | |
10 ./activity_monitor.py --hz=20 --duration=5 --outfile=/tmp/foo | |
11 """ | |
12 | |
13 import collections | |
14 import json | |
15 import optparse | |
16 import os | |
17 import subprocess | |
18 import sys | |
19 import time | |
20 import urllib | |
21 | |
22 sys.path.append(os.path.join(sys.path[0], '..', '..', '..', 'build','android')) | |
23 from pylib import android_commands | |
bulach
2012/07/16 17:52:26
nit: while at it,
from pylib import constants
| |
24 from pylib import io_stats_parser | |
25 | |
26 | |
27 ACTIVITY_MONITOR_DEVICE_PATH = '/data/local/tmp/activity_monitor' | |
28 ACTIVITY_MONITOR_HOST_PATH = os.path.abspath(os.path.join( | |
29 os.path.dirname(os.path.realpath(__file__)), '..', '..', '..', | |
bulach
2012/07/16 17:52:26
nit: pylib.constants.CHROME_DIR
| |
30 'out', 'Release', 'activity_monitor')) | |
31 PROFILE_PATH = '/sdcard/Download/activity_monitor.profile' | |
32 RESULT_VIEWER_PATH = os.path.abspath(os.path.join( | |
33 os.path.dirname(os.path.realpath(__file__)), 'activity_monitor.html')) | |
34 | |
35 | |
36 class ActivityMonitor(object): | |
37 | |
38 def __init__(self, adb=None, hz=20): | |
bulach
2012/07/16 17:52:26
I just noticed that this is used by build/android/
| |
39 """Create an ActivityMonitor | |
40 | |
41 Args | |
42 adb: Instance of AndroidComannds. | |
43 hz: Frequency at which to sample activity. | |
44 """ | |
45 self._adb = adb or android_commands.AndroidCommands() | |
46 self._adb.PushIfNeeded(ACTIVITY_MONITOR_HOST_PATH, | |
47 ACTIVITY_MONITOR_DEVICE_PATH) | |
48 self._hz = hz | |
49 | |
50 def Start(self): | |
51 """Starts ActivityMonitor on the device.""" | |
52 self._adb.SetFileContents(PROFILE_PATH, '') | |
53 self._process = subprocess.Popen( | |
54 ['adb', 'shell', '%s --hz=%d %s' % ( | |
55 ACTIVITY_MONITOR_DEVICE_PATH, self._hz, PROFILE_PATH)]) | |
56 | |
57 def StopAndCollect(self, output_path): | |
58 """Stops monitoring and saves results. | |
59 | |
60 Args: | |
61 output_path: Path to save results. | |
62 | |
63 Returns: | |
64 String of URL to load results in browser. | |
65 """ | |
66 assert self._process | |
67 self._adb.KillAll(ACTIVITY_MONITOR_DEVICE_PATH) | |
68 self._process.wait() | |
69 profile = self._adb.GetFileContents(PROFILE_PATH) | |
70 | |
71 results = collections.defaultdict(list) | |
72 last_io_stats = None | |
73 last_cpu_stats = None | |
74 for line in profile: | |
75 if ' mmcblk0 ' in line: | |
76 stats = io_stats_parser.ParseIoStatsLine(line) | |
77 if last_io_stats: | |
78 results['sectors_read'].append(stats.num_sectors_read - | |
79 last_io_stats.num_sectors_read) | |
80 results['sectors_written'].append(stats.num_sectors_written - | |
81 last_io_stats.num_sectors_written) | |
82 last_io_stats = stats | |
83 elif line.startswith('cpu '): | |
84 stats = self.ParseCpuStatsLine(line) | |
85 if last_cpu_stats: | |
86 results['user'].append(stats.user - last_cpu_stats.user) | |
87 results['nice'].append(stats.nice - last_cpu_stats.nice) | |
88 results['system'].append(stats.system - last_cpu_stats.system) | |
89 results['idle'].append(stats.idle - last_cpu_stats.idle) | |
90 results['iowait'].append(stats.iowait - last_cpu_stats.iowait) | |
91 results['irq'].append(stats.irq - last_cpu_stats.irq) | |
92 results['softirq'].append(stats.softirq- last_cpu_stats.softirq) | |
93 last_cpu_stats = stats | |
94 units = { | |
95 'sectors_read': 'sectors', | |
96 'sectors_written': 'sectors', | |
97 'user': 'jiffies', | |
98 'nice': 'jiffies', | |
99 'system': 'jiffies', | |
100 'idle': 'jiffies', | |
101 'iowait': 'jiffies', | |
102 'irq': 'jiffies', | |
103 'softirq': 'jiffies', | |
104 } | |
105 with open(output_path, 'w') as f: | |
106 f.write('display(%d, %s, %s);' % (self._hz, json.dumps(results), units)) | |
107 return 'file://%s?results=file://%s' % (RESULT_VIEWER_PATH, | |
108 urllib.quote(output_path)) | |
109 | |
110 | |
111 @staticmethod | |
112 def ParseCpuStatsLine(line): | |
bulach
2012/07/16 17:52:26
nit: add a _ prefix
| |
113 """Parses a line of cpu stats into a CpuStats named tuple.""" | |
114 # Field definitions: http://www.linuxhowtos.org/System/procstat.htm | |
115 CpuStats = collections.namedtuple('CpuStats', | |
bulach
2012/07/16 17:52:26
nit: cpu_stats
| |
116 ['device', | |
117 'user', | |
118 'nice', | |
119 'system', | |
120 'idle', | |
121 'iowait', | |
122 'irq', | |
123 'softirq', | |
124 ]) | |
125 fields = line.split() | |
126 return CpuStats._make([fields[0]] + [int(f) for f in fields[1:8]]) | |
127 | |
128 | |
129 def main(argv): | |
130 option_parser = optparse.OptionParser() | |
131 option_parser.add_option('--hz', type='int', default=20, | |
132 help='Number of samples/sec.') | |
133 option_parser.add_option('--duration', type='int', default=5, | |
134 help='Seconds to monitor.') | |
135 option_parser.add_option('--outfile', default='/tmp/activitymonitor', | |
136 help='Location to start output file.') | |
137 options, args = option_parser.parse_args(argv) | |
138 | |
139 activity_monitor = ActivityMonitor(hz=options.hz) | |
140 activity_monitor.Start() | |
141 print 'Waiting for %d seconds while profiling.' % options.duration | |
142 time.sleep(options.duration) | |
143 url = activity_monitor.StopAndCollect(options.outfile) | |
144 print 'View results in browser at %s' % url | |
145 | |
146 if __name__ == '__main__': | |
147 sys.exit(main(sys.argv)) | |
OLD | NEW |