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