Home | History | Annotate | Download | only in platform
      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