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