summaryrefslogtreecommitdiffstats
path: root/build/android/pylib/device_stats_monitor.py
blob: 2a7adc8309d85d526be7c745b46872ea9ee6afca (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
# 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.

"""Utilities for iotop/top style profiling for android."""

import collections
import json
import os
import subprocess
import sys
import urllib

import constants
import io_stats_parser


class DeviceStatsMonitor(object):
  """Class for collecting device stats such as IO/CPU usage.

  Args:
      adb: Instance of AndroidComannds.
      hz: Frequency at which to sample device stats.
  """

  DEVICE_PATH = constants.TEST_EXECUTABLE_DIR + '/device_stats_monitor'
  PROFILE_PATH = (constants.DEVICE_PERF_OUTPUT_DIR +
      '/device_stats_monitor.profile')
  RESULT_VIEWER_PATH = os.path.abspath(os.path.join(
      os.path.dirname(os.path.realpath(__file__)), 'device_stats_monitor.html'))

  def __init__(self, adb, hz):
    self._adb = adb
    host_path = os.path.join(
        constants.GetOutDirectory(), 'device_stats_monitor')
    self._adb.PushIfNeeded(host_path, DeviceStatsMonitor.DEVICE_PATH)
    self._hz = hz

  def Start(self):
    """Starts device stats monitor on the device."""
    self._adb.SetProtectedFileContents(DeviceStatsMonitor.PROFILE_PATH, '')
    self._process = subprocess.Popen(
        ['adb', 'shell', '%s --hz=%d %s' % (
            DeviceStatsMonitor.DEVICE_PATH, self._hz,
            DeviceStatsMonitor.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(DeviceStatsMonitor.DEVICE_PATH)
    self._process.wait()
    profile = self._adb.GetFileContents(DeviceStatsMonitor.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' % (
        DeviceStatsMonitor.RESULT_VIEWER_PATH, urllib.quote(output_path))


  @staticmethod
  def _ParseCpuStatsLine(line):
    """Parses a line of cpu stats into a CpuStats named tuple."""
    # Field definitions: http://www.linuxhowtos.org/System/procstat.htm
    cpu_stats = collections.namedtuple('CpuStats',
                                       ['device',
                                        'user',
                                        'nice',
                                        'system',
                                        'idle',
                                        'iowait',
                                        'irq',
                                        'softirq',
                                       ])
    fields = line.split()
    return cpu_stats._make([fields[0]] + [int(f) for f in fields[1:8]])