-# Copyright (c) 2013 The Chromium Authors. All rights reserved.
+# Copyright 2013 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
import ctypes
-import logging
import os
-import plistlib
-import signal
-import subprocess
-import tempfile
+import platform
import time
+
+from telemetry import decorators
+from telemetry.core.platform import platform_backend
+from telemetry.core.platform import posix_platform_backend
+from telemetry.core.platform import process_statistic_timeline_data
+from telemetry.core.platform.power_monitor import powermetrics_power_monitor
+
try:
import resource # pylint: disable=F0401
except ImportError:
resource = None # Not available on all platforms
-from ctypes import util
-from telemetry.core.platform import posix_platform_backend
-
-class MacPlatformBackend(posix_platform_backend.PosixPlatformBackend):
- class PowerMetricsUtility(object):
- def __init__(self):
- self._powermetrics_process = None
- self._powermetrics_output_file = None
-
- @property
- def binary_path(self):
- return '/usr/bin/powermetrics'
-
- def StartMonitoringPowerAsync(self):
- assert not self._powermetrics_process, (
- "Must call StopMonitoringPowerAsync().")
- SAMPLE_INTERVAL_MS = 1000 / 20 # 20 Hz, arbitrary.
- self._powermetrics_output_file = tempfile.NamedTemporaryFile()
- args = [self.binary_path, '-f', 'plist', '-i',
- '%d' % SAMPLE_INTERVAL_MS, '-u', self._powermetrics_output_file.name]
-
- # powermetrics writes lots of output to stderr, don't echo unless verbose
- # logging enabled.
- stderror_destination = subprocess.PIPE
- if logging.getLogger().isEnabledFor(logging.DEBUG):
- stderror_destination = None
-
- self._powermetrics_process = subprocess.Popen(args,
- stdout=subprocess.PIPE, stderr=stderror_destination)
-
- def StopMonitoringPowerAsync(self):
- assert self._powermetrics_process, (
- "StartMonitoringPowerAsync() not called.")
- # Tell powermetrics to take an immediate sample.
- try:
- self._powermetrics_process.send_signal(signal.SIGINFO)
- self._powermetrics_process.send_signal(signal.SIGTERM)
- returncode = self._powermetrics_process.wait()
- assert returncode in [0, -15], (
- "powermetrics return code: %d" % returncode)
- return open(self._powermetrics_output_file.name, 'r').read()
- finally:
- self._powermetrics_output_file.close()
- self._powermetrics_output_file = None
- self._powermetrics_process = None
+class MacPlatformBackend(posix_platform_backend.PosixPlatformBackend):
def __init__(self):
super(MacPlatformBackend, self).__init__()
self.libproc = None
- self.powermetrics_tool_ = MacPlatformBackend.PowerMetricsUtility()
+ self._power_monitor = powermetrics_power_monitor.PowerMetricsPowerMonitor(
+ self)
def StartRawDisplayFrameRateMeasurement(self):
raise NotImplementedError()
def HasBeenThermallyThrottled(self):
raise NotImplementedError()
+ def _GetIdleWakeupCount(self, pid):
+ top_output = self._GetTopOutput(pid, ['idlew'])
+
+ # Sometimes top won't return anything here, just ignore such cases -
+ # crbug.com/354812 .
+ if top_output[-2] != 'IDLEW':
+ return process_statistic_timeline_data.IdleWakeupTimelineData(pid, 0)
+ # Numbers reported by top may have a '+' appended.
+ wakeup_count = int(top_output[-1].strip('+ '))
+ return process_statistic_timeline_data.IdleWakeupTimelineData(pid,
+ wakeup_count)
+
def GetCpuStats(self, pid):
- """Return current cpu processing time of pid in seconds."""
+ """Returns a dict of cpu statistics for the process represented by |pid|."""
class ProcTaskInfo(ctypes.Structure):
"""Struct for proc_pidinfo() call."""
_fields_ = [("pti_virtual_size", ctypes.c_uint64),
proc_info = ProcTaskInfo()
if not self.libproc:
- self.libproc = ctypes.CDLL(util.find_library('libproc'))
+ self.libproc = ctypes.CDLL(ctypes.util.find_library('libproc'))
self.libproc.proc_pidinfo(pid, proc_info.PROC_PIDTASKINFO, 0,
ctypes.byref(proc_info), proc_info.size)
- # Convert nanoseconds to seconds
+ # Convert nanoseconds to seconds.
cpu_time = (proc_info.pti_total_user / 1000000000.0 +
proc_info.pti_total_system / 1000000000.0)
- return {'CpuProcessTime': cpu_time}
+ results = {'CpuProcessTime': cpu_time,
+ 'ContextSwitches': proc_info.pti_csw}
+
+ # top only reports idle wakeup count starting from OS X 10.9.
+ if self.GetOSVersionName() >= platform_backend.MAVERICKS:
+ results.update({'IdleWakeupCount': self._GetIdleWakeupCount(pid)})
+ return results
def GetCpuTimestamp(self):
"""Return current timestamp in seconds."""
return {'TotalTime': time.time()}
def GetSystemCommitCharge(self):
- vm_stat = self._RunCommand(['vm_stat'])
+ vm_stat = self.RunCommand(['vm_stat'])
for stat in vm_stat.splitlines():
key, value = stat.split(':')
if key == 'Pages active':
return pages_active * resource.getpagesize() / 1024
return 0
+ @decorators.Cache
+ def GetSystemTotalPhysicalMemory(self):
+ return int(self.RunCommand(['sysctl', '-n', 'hw.memsize']))
+
def PurgeUnpinnedMemory(self):
# TODO(pliard): Implement this.
pass
def GetMemoryStats(self, pid):
- rss_vsz = self._GetPsOutput(['rss', 'vsz'], pid)
+ rss_vsz = self.GetPsOutput(['rss', 'vsz'], pid)
if rss_vsz:
rss, vsz = rss_vsz[0].split()
return {'VM': 1024 * int(vsz),
'WorkingSetSize': 1024 * int(rss)}
return {}
+ @decorators.Cache
+ def GetArchName(self):
+ return platform.machine()
+
def GetOSName(self):
return 'mac'
+ @decorators.Cache
def GetOSVersionName(self):
os_version = os.uname()[2]
if os_version.startswith('9.'):
- return 'leopard'
+ return platform_backend.LEOPARD
if os_version.startswith('10.'):
- return 'snowleopard'
+ return platform_backend.SNOWLEOPARD
if os_version.startswith('11.'):
- return 'lion'
+ return platform_backend.LION
if os_version.startswith('12.'):
- return 'mountainlion'
+ return platform_backend.MOUNTAINLION
if os_version.startswith('13.'):
- return 'mavericks'
+ return platform_backend.MAVERICKS
+ if os_version.startswith('14.'):
+ return platform_backend.YOSEMITE
- raise NotImplementedError("Unknown OS X version %s." % os_version)
+ raise NotImplementedError('Unknown mac version %s.' % os_version)
def CanFlushIndividualFilesFromSystemCache(self):
return False
def FlushEntireSystemCache(self):
- p = subprocess.Popen(['purge'])
- p.wait()
+ mavericks_or_later = self.GetOSVersionName() >= platform_backend.MAVERICKS
+ p = self.LaunchApplication('purge', elevate_privilege=mavericks_or_later)
+ p.communicate()
assert p.returncode == 0, 'Failed to flush system cache'
- def CanMonitorPowerAsync(self):
- # powermetrics only runs on OS X version >= 10.9 .
- os_version = int(os.uname()[2].split('.')[0])
- binary_path = self.powermetrics_tool_.binary_path
- return os_version >= 13 and self.CanLaunchApplication(binary_path)
-
- def SetPowerMetricsUtilityForTest(self, obj):
- self.powermetrics_tool_ = obj
-
- def StartMonitoringPowerAsync(self):
- self.powermetrics_tool_.StartMonitoringPowerAsync()
-
- def _ParsePowerMetricsOutput(self, powermetrics_output):
- """Parse output of powermetrics command line utility.
-
- Returns:
- Dictionary in the format returned by StopMonitoringPowerAsync().
- """
- power_samples = []
- total_energy_consumption_mwh = 0
- # powermetrics outputs multiple PLists separated by null terminators.
- raw_plists = powermetrics_output.split('\0')[:-1]
- for raw_plist in raw_plists:
- plist = plistlib.readPlistFromString(raw_plist)
-
- # Duration of this sample.
- sample_duration_ms = int(plist['elapsed_ns']) / 10**6
-
- if 'processor' not in plist:
- continue
- processor = plist['processor']
-
- energy_consumption_mw = int(processor.get('package_watts', 0)) * 10**3
-
- total_energy_consumption_mwh += (energy_consumption_mw *
- (sample_duration_ms / 3600000.))
-
- power_samples.append(energy_consumption_mw)
+ def CanMonitorPower(self):
+ return self._power_monitor.CanMonitorPower()
- # -------- Collect and Process Data -------------
- out_dict = {}
- # Raw power usage samples.
- if power_samples:
- out_dict['power_samples_mw'] = power_samples
- out_dict['energy_consumption_mwh'] = total_energy_consumption_mwh
+ def CanMeasurePerApplicationPower(self):
+ return self._power_monitor.CanMeasurePerApplicationPower()
- return out_dict
+ def StartMonitoringPower(self, browser):
+ self._power_monitor.StartMonitoringPower(browser)
- def StopMonitoringPowerAsync(self):
- powermetrics_output = self.powermetrics_tool_.StopMonitoringPowerAsync()
- assert len(powermetrics_output) > 0
- return self._ParsePowerMetricsOutput(powermetrics_output)
+ def StopMonitoringPower(self):
+ return self._power_monitor.StopMonitoringPower()