OLD | NEW |
| (Empty) |
1 # Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
2 # Use of this source code is governed by a BSD-style license that can be | |
3 # found in the LICENSE file. | |
4 | |
5 """Utilities for iotop/top style profiling for android.""" | |
6 | |
7 import collections | |
8 import json | |
9 import os | |
10 import subprocess | |
11 import sys | |
12 import urllib | |
13 | |
14 import constants | |
15 import io_stats_parser | |
16 | |
17 | |
18 class DeviceStatsMonitor(object): | |
19 """Class for collecting device stats such as IO/CPU usage. | |
20 | |
21 Args: | |
22 adb: Instance of AndroidComannds. | |
23 hz: Frequency at which to sample device stats. | |
24 """ | |
25 | |
26 DEVICE_PATH = constants.TEST_EXECUTABLE_DIR + '/device_stats_monitor' | |
27 PROFILE_PATH = (constants.DEVICE_PERF_OUTPUT_DIR + | |
28 '/device_stats_monitor.profile') | |
29 RESULT_VIEWER_PATH = os.path.abspath(os.path.join( | |
30 os.path.dirname(os.path.realpath(__file__)), 'device_stats_monitor.html')) | |
31 | |
32 def __init__(self, adb, hz): | |
33 self._adb = adb | |
34 host_path = os.path.join( | |
35 constants.GetOutDirectory(), 'device_stats_monitor') | |
36 self._adb.PushIfNeeded(host_path, DeviceStatsMonitor.DEVICE_PATH) | |
37 self._hz = hz | |
38 | |
39 def Start(self): | |
40 """Starts device stats monitor on the device.""" | |
41 self._adb.SetProtectedFileContents(DeviceStatsMonitor.PROFILE_PATH, '') | |
42 self._process = subprocess.Popen( | |
43 ['adb', 'shell', '%s --hz=%d %s' % ( | |
44 DeviceStatsMonitor.DEVICE_PATH, self._hz, | |
45 DeviceStatsMonitor.PROFILE_PATH)]) | |
46 | |
47 def StopAndCollect(self, output_path): | |
48 """Stops monitoring and saves results. | |
49 | |
50 Args: | |
51 output_path: Path to save results. | |
52 | |
53 Returns: | |
54 String of URL to load results in browser. | |
55 """ | |
56 assert self._process | |
57 self._adb.KillAll(DeviceStatsMonitor.DEVICE_PATH) | |
58 self._process.wait() | |
59 profile = self._adb.GetFileContents(DeviceStatsMonitor.PROFILE_PATH) | |
60 | |
61 results = collections.defaultdict(list) | |
62 last_io_stats = None | |
63 last_cpu_stats = None | |
64 for line in profile: | |
65 if ' mmcblk0 ' in line: | |
66 stats = io_stats_parser.ParseIoStatsLine(line) | |
67 if last_io_stats: | |
68 results['sectors_read'].append(stats.num_sectors_read - | |
69 last_io_stats.num_sectors_read) | |
70 results['sectors_written'].append(stats.num_sectors_written - | |
71 last_io_stats.num_sectors_written) | |
72 last_io_stats = stats | |
73 elif line.startswith('cpu '): | |
74 stats = self._ParseCpuStatsLine(line) | |
75 if last_cpu_stats: | |
76 results['user'].append(stats.user - last_cpu_stats.user) | |
77 results['nice'].append(stats.nice - last_cpu_stats.nice) | |
78 results['system'].append(stats.system - last_cpu_stats.system) | |
79 results['idle'].append(stats.idle - last_cpu_stats.idle) | |
80 results['iowait'].append(stats.iowait - last_cpu_stats.iowait) | |
81 results['irq'].append(stats.irq - last_cpu_stats.irq) | |
82 results['softirq'].append(stats.softirq- last_cpu_stats.softirq) | |
83 last_cpu_stats = stats | |
84 units = { | |
85 'sectors_read': 'sectors', | |
86 'sectors_written': 'sectors', | |
87 'user': 'jiffies', | |
88 'nice': 'jiffies', | |
89 'system': 'jiffies', | |
90 'idle': 'jiffies', | |
91 'iowait': 'jiffies', | |
92 'irq': 'jiffies', | |
93 'softirq': 'jiffies', | |
94 } | |
95 with open(output_path, 'w') as f: | |
96 f.write('display(%d, %s, %s);' % (self._hz, json.dumps(results), units)) | |
97 return 'file://%s?results=file://%s' % ( | |
98 DeviceStatsMonitor.RESULT_VIEWER_PATH, urllib.quote(output_path)) | |
99 | |
100 | |
101 @staticmethod | |
102 def _ParseCpuStatsLine(line): | |
103 """Parses a line of cpu stats into a CpuStats named tuple.""" | |
104 # Field definitions: http://www.linuxhowtos.org/System/procstat.htm | |
105 cpu_stats = collections.namedtuple('CpuStats', | |
106 ['device', | |
107 'user', | |
108 'nice', | |
109 'system', | |
110 'idle', | |
111 'iowait', | |
112 'irq', | |
113 'softirq', | |
114 ]) | |
115 fields = line.split() | |
116 return cpu_stats._make([fields[0]] + [int(f) for f in fields[1:8]]) | |
OLD | NEW |