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