1 # Copyright (c) 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.
14 import resource # pylint: disable=F0401
16 resource = None # Not available on all platforms
18 from ctypes import util
19 from telemetry.core.platform import posix_platform_backend
21 class MacPlatformBackend(posix_platform_backend.PosixPlatformBackend):
23 class PowerMetricsUtility(object):
25 self._powermetrics_process = None
26 self._powermetrics_output_file = None
29 def binary_path(self):
30 return '/usr/bin/powermetrics'
32 def StartMonitoringPowerAsync(self):
33 assert not self._powermetrics_process, (
34 "Must call StopMonitoringPowerAsync().")
35 SAMPLE_INTERVAL_MS = 1000 / 20 # 20 Hz, arbitrary.
36 self._powermetrics_output_file = tempfile.NamedTemporaryFile()
37 args = [self.binary_path, '-f', 'plist', '-i',
38 '%d' % SAMPLE_INTERVAL_MS, '-u', self._powermetrics_output_file.name]
40 # powermetrics writes lots of output to stderr, don't echo unless verbose
42 stderror_destination = subprocess.PIPE
43 if logging.getLogger().isEnabledFor(logging.DEBUG):
44 stderror_destination = None
46 self._powermetrics_process = subprocess.Popen(args,
47 stdout=subprocess.PIPE, stderr=stderror_destination)
49 def StopMonitoringPowerAsync(self):
50 assert self._powermetrics_process, (
51 "StartMonitoringPowerAsync() not called.")
52 # Tell powermetrics to take an immediate sample.
54 self._powermetrics_process.send_signal(signal.SIGINFO)
55 self._powermetrics_process.send_signal(signal.SIGTERM)
56 returncode = self._powermetrics_process.wait()
57 assert returncode in [0, -15], (
58 "powermetrics return code: %d" % returncode)
59 return open(self._powermetrics_output_file.name, 'r').read()
61 self._powermetrics_output_file.close()
62 self._powermetrics_output_file = None
63 self._powermetrics_process = None
66 super(MacPlatformBackend, self).__init__()
68 self.powermetrics_tool_ = MacPlatformBackend.PowerMetricsUtility()
70 def StartRawDisplayFrameRateMeasurement(self):
71 raise NotImplementedError()
73 def StopRawDisplayFrameRateMeasurement(self):
74 raise NotImplementedError()
76 def GetRawDisplayFrameRateMeasurements(self):
77 raise NotImplementedError()
79 def IsThermallyThrottled(self):
80 raise NotImplementedError()
82 def HasBeenThermallyThrottled(self):
83 raise NotImplementedError()
85 def GetCpuStats(self, pid):
86 """Return current cpu processing time of pid in seconds."""
87 class ProcTaskInfo(ctypes.Structure):
88 """Struct for proc_pidinfo() call."""
89 _fields_ = [("pti_virtual_size", ctypes.c_uint64),
90 ("pti_resident_size", ctypes.c_uint64),
91 ("pti_total_user", ctypes.c_uint64),
92 ("pti_total_system", ctypes.c_uint64),
93 ("pti_threads_user", ctypes.c_uint64),
94 ("pti_threads_system", ctypes.c_uint64),
95 ("pti_policy", ctypes.c_int32),
96 ("pti_faults", ctypes.c_int32),
97 ("pti_pageins", ctypes.c_int32),
98 ("pti_cow_faults", ctypes.c_int32),
99 ("pti_messages_sent", ctypes.c_int32),
100 ("pti_messages_received", ctypes.c_int32),
101 ("pti_syscalls_mach", ctypes.c_int32),
102 ("pti_syscalls_unix", ctypes.c_int32),
103 ("pti_csw", ctypes.c_int32),
104 ("pti_threadnum", ctypes.c_int32),
105 ("pti_numrunning", ctypes.c_int32),
106 ("pti_priority", ctypes.c_int32)]
109 self.size = ctypes.sizeof(self)
110 super(ProcTaskInfo, self).__init__()
112 proc_info = ProcTaskInfo()
114 self.libproc = ctypes.CDLL(util.find_library('libproc'))
115 self.libproc.proc_pidinfo(pid, proc_info.PROC_PIDTASKINFO, 0,
116 ctypes.byref(proc_info), proc_info.size)
118 # Convert nanoseconds to seconds
119 cpu_time = (proc_info.pti_total_user / 1000000000.0 +
120 proc_info.pti_total_system / 1000000000.0)
121 return {'CpuProcessTime': cpu_time}
123 def GetCpuTimestamp(self):
124 """Return current timestamp in seconds."""
125 return {'TotalTime': time.time()}
127 def GetSystemCommitCharge(self):
128 vm_stat = self._RunCommand(['vm_stat'])
129 for stat in vm_stat.splitlines():
130 key, value = stat.split(':')
131 if key == 'Pages active':
132 pages_active = int(value.strip()[:-1]) # Strip trailing '.'
133 return pages_active * resource.getpagesize() / 1024
136 def PurgeUnpinnedMemory(self):
137 # TODO(pliard): Implement this.
140 def GetMemoryStats(self, pid):
141 rss_vsz = self._GetPsOutput(['rss', 'vsz'], pid)
143 rss, vsz = rss_vsz[0].split()
144 return {'VM': 1024 * int(vsz),
145 'WorkingSetSize': 1024 * int(rss)}
151 def GetOSVersionName(self):
152 os_version = os.uname()[2]
154 if os_version.startswith('9.'):
156 if os_version.startswith('10.'):
158 if os_version.startswith('11.'):
160 if os_version.startswith('12.'):
161 return 'mountainlion'
162 if os_version.startswith('13.'):
165 raise NotImplementedError("Unknown OS X version %s." % os_version)
167 def CanFlushIndividualFilesFromSystemCache(self):
170 def FlushEntireSystemCache(self):
171 p = subprocess.Popen(['purge'])
173 assert p.returncode == 0, 'Failed to flush system cache'
175 def CanMonitorPowerAsync(self):
176 # powermetrics only runs on OS X version >= 10.9 .
177 os_version = int(os.uname()[2].split('.')[0])
178 binary_path = self.powermetrics_tool_.binary_path
179 return os_version >= 13 and self.CanLaunchApplication(binary_path)
181 def SetPowerMetricsUtilityForTest(self, obj):
182 self.powermetrics_tool_ = obj
184 def StartMonitoringPowerAsync(self):
185 self.powermetrics_tool_.StartMonitoringPowerAsync()
187 def _ParsePowerMetricsOutput(self, powermetrics_output):
188 """Parse output of powermetrics command line utility.
191 Dictionary in the format returned by StopMonitoringPowerAsync().
194 total_energy_consumption_mwh = 0
195 # powermetrics outputs multiple PLists separated by null terminators.
196 raw_plists = powermetrics_output.split('\0')[:-1]
197 for raw_plist in raw_plists:
198 plist = plistlib.readPlistFromString(raw_plist)
200 # Duration of this sample.
201 sample_duration_ms = int(plist['elapsed_ns']) / 10**6
203 if 'processor' not in plist:
205 processor = plist['processor']
207 energy_consumption_mw = int(processor.get('package_watts', 0)) * 10**3
209 total_energy_consumption_mwh += (energy_consumption_mw *
210 (sample_duration_ms / 3600000.))
212 power_samples.append(energy_consumption_mw)
214 # -------- Collect and Process Data -------------
216 # Raw power usage samples.
218 out_dict['power_samples_mw'] = power_samples
219 out_dict['energy_consumption_mwh'] = total_energy_consumption_mwh
223 def StopMonitoringPowerAsync(self):
224 powermetrics_output = self.powermetrics_tool_.StopMonitoringPowerAsync()
225 assert len(powermetrics_output) > 0
226 return self._ParsePowerMetricsOutput(powermetrics_output)