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