|
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, build_type): |
|
33 self._adb = adb |
|
34 host_path = os.path.abspath(os.path.join( |
|
35 constants.CHROME_DIR, 'out', build_type, '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.SetFileContents(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]]) |