• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2013 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
5import ctypes
6import os
7import platform
8import subprocess
9import sys
10import time
11
12from telemetry.core import os_version as os_version_module
13from telemetry import decorators
14from telemetry.internal.platform import posix_platform_backend
15from telemetry.internal.platform.power_monitor import powermetrics_power_monitor
16from telemetry.util import process_statistic_timeline_data
17
18try:
19  import resource  # pylint: disable=import-error
20except ImportError:
21  resource = None  # Not available on all platforms
22
23
24
25class MacPlatformBackend(posix_platform_backend.PosixPlatformBackend):
26  def __init__(self):
27    super(MacPlatformBackend, self).__init__()
28    self.libproc = None
29    self._power_monitor = powermetrics_power_monitor.PowerMetricsPowerMonitor(
30        self)
31
32  def GetSystemLog(self):
33    # Since the log file can be very large, only show the last 200 lines.
34    return subprocess.check_output(
35        ['tail', '-n', '200', '/var/log/system.log'])
36
37  @classmethod
38  def IsPlatformBackendForHost(cls):
39    return sys.platform == 'darwin'
40
41  def IsThermallyThrottled(self):
42    raise NotImplementedError()
43
44  def HasBeenThermallyThrottled(self):
45    raise NotImplementedError()
46
47  def _GetIdleWakeupCount(self, pid):
48    top_output = self._GetTopOutput(pid, ['idlew'])
49
50    # Sometimes top won't return anything here, just ignore such cases -
51    # crbug.com/354812 .
52    if top_output[-2] != 'IDLEW':
53      return process_statistic_timeline_data.IdleWakeupTimelineData(pid, 0)
54    # Numbers reported by top may have a '+' appended.
55    wakeup_count = int(top_output[-1].strip('+ '))
56    return process_statistic_timeline_data.IdleWakeupTimelineData(pid,
57        wakeup_count)
58
59  def GetCpuStats(self, pid):
60    """Returns a dict of cpu statistics for the process represented by |pid|."""
61    class ProcTaskInfo(ctypes.Structure):
62      """Struct for proc_pidinfo() call."""
63      _fields_ = [("pti_virtual_size", ctypes.c_uint64),
64                  ("pti_resident_size", ctypes.c_uint64),
65                  ("pti_total_user", ctypes.c_uint64),
66                  ("pti_total_system", ctypes.c_uint64),
67                  ("pti_threads_user", ctypes.c_uint64),
68                  ("pti_threads_system", ctypes.c_uint64),
69                  ("pti_policy", ctypes.c_int32),
70                  ("pti_faults", ctypes.c_int32),
71                  ("pti_pageins", ctypes.c_int32),
72                  ("pti_cow_faults", ctypes.c_int32),
73                  ("pti_messages_sent", ctypes.c_int32),
74                  ("pti_messages_received", ctypes.c_int32),
75                  ("pti_syscalls_mach", ctypes.c_int32),
76                  ("pti_syscalls_unix", ctypes.c_int32),
77                  ("pti_csw", ctypes.c_int32),
78                  ("pti_threadnum", ctypes.c_int32),
79                  ("pti_numrunning", ctypes.c_int32),
80                  ("pti_priority", ctypes.c_int32)]
81      PROC_PIDTASKINFO = 4
82      def __init__(self):
83        self.size = ctypes.sizeof(self)
84        super(ProcTaskInfo, self).__init__()  # pylint: disable=bad-super-call
85
86    proc_info = ProcTaskInfo()
87    if not self.libproc:
88      self.libproc = ctypes.CDLL(ctypes.util.find_library('libproc'))
89    self.libproc.proc_pidinfo(pid, proc_info.PROC_PIDTASKINFO, 0,
90                              ctypes.byref(proc_info), proc_info.size)
91
92    # Convert nanoseconds to seconds.
93    cpu_time = (proc_info.pti_total_user / 1000000000.0 +
94                proc_info.pti_total_system / 1000000000.0)
95    results = {'CpuProcessTime': cpu_time,
96               'ContextSwitches': proc_info.pti_csw}
97
98    # top only reports idle wakeup count starting from OS X 10.9.
99    if self.GetOSVersionName() >= os_version_module.MAVERICKS:
100      results.update({'IdleWakeupCount': self._GetIdleWakeupCount(pid)})
101    return results
102
103  def GetCpuTimestamp(self):
104    """Return current timestamp in seconds."""
105    return {'TotalTime': time.time()}
106
107  def GetSystemCommitCharge(self):
108    vm_stat = self.RunCommand(['vm_stat'])
109    for stat in vm_stat.splitlines():
110      key, value = stat.split(':')
111      if key == 'Pages active':
112        pages_active = int(value.strip()[:-1])  # Strip trailing '.'
113        return pages_active * resource.getpagesize() / 1024
114    return 0
115
116  @decorators.Cache
117  def GetSystemTotalPhysicalMemory(self):
118    return int(self.RunCommand(['sysctl', '-n', 'hw.memsize']))
119
120  def PurgeUnpinnedMemory(self):
121    # TODO(pliard): Implement this.
122    pass
123
124  @decorators.Deprecated(
125      2017, 11, 4,
126      'Clients should use tracing and memory-infra in new Telemetry '
127      'benchmarks. See for context: https://crbug.com/632021')
128  def GetMemoryStats(self, pid):
129    rss_vsz = self.GetPsOutput(['rss', 'vsz'], pid)
130    if rss_vsz:
131      rss, vsz = rss_vsz[0].split()
132      return {'VM': 1024 * int(vsz),
133              'WorkingSetSize': 1024 * int(rss)}
134    return {}
135
136  @decorators.Cache
137  def GetArchName(self):
138    return platform.machine()
139
140  def GetOSName(self):
141    return 'mac'
142
143  @decorators.Cache
144  def GetOSVersionName(self):
145    os_version = os.uname()[2]
146
147    if os_version.startswith('9.'):
148      return os_version_module.LEOPARD
149    if os_version.startswith('10.'):
150      return os_version_module.SNOWLEOPARD
151    if os_version.startswith('11.'):
152      return os_version_module.LION
153    if os_version.startswith('12.'):
154      return os_version_module.MOUNTAINLION
155    if os_version.startswith('13.'):
156      return os_version_module.MAVERICKS
157    if os_version.startswith('14.'):
158      return os_version_module.YOSEMITE
159    if os_version.startswith('15.'):
160      return os_version_module.ELCAPITAN
161    if os_version.startswith('16.'):
162      return os_version_module.SIERRA
163
164    raise NotImplementedError('Unknown mac version %s.' % os_version)
165
166  def CanTakeScreenshot(self):
167    return True
168
169  def TakeScreenshot(self, file_path):
170    return subprocess.call(['screencapture', file_path])
171
172  def CanFlushIndividualFilesFromSystemCache(self):
173    return False
174
175  def SupportFlushEntireSystemCache(self):
176    return self.HasRootAccess()
177
178  def FlushEntireSystemCache(self):
179    mavericks_or_later = self.GetOSVersionName() >= os_version_module.MAVERICKS
180    p = self.LaunchApplication('purge', elevate_privilege=mavericks_or_later)
181    p.communicate()
182    assert p.returncode == 0, 'Failed to flush system cache'
183
184  def CanMonitorPower(self):
185    return self._power_monitor.CanMonitorPower()
186
187  def CanMeasurePerApplicationPower(self):
188    return self._power_monitor.CanMeasurePerApplicationPower()
189
190  def StartMonitoringPower(self, browser):
191    self._power_monitor.StartMonitoringPower(browser)
192
193  def StopMonitoringPower(self):
194    return self._power_monitor.StopMonitoringPower()
195