eb99577ce2a56fec2b86bf8b05d0d653d0c3fda2
[platform/framework/web/crosswalk.git] / src / tools / telemetry / telemetry / core / platform / mac_platform_backend.py
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.
4
5 import ctypes
6 import logging
7 import os
8 import plistlib
9 import signal
10 import subprocess
11 import tempfile
12 import time
13 try:
14   import resource  # pylint: disable=F0401
15 except ImportError:
16   resource = None  # Not available on all platforms
17
18 from ctypes import util
19 from telemetry.core.platform import posix_platform_backend
20
21 class MacPlatformBackend(posix_platform_backend.PosixPlatformBackend):
22
23   class PowerMetricsUtility(object):
24     def __init__(self):
25       self._powermetrics_process = None
26       self._powermetrics_output_file = None
27
28     @property
29     def binary_path(self):
30       return '/usr/bin/powermetrics'
31
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]
39
40       # powermetrics writes lots of output to stderr, don't echo unless verbose
41       # logging enabled.
42       stderror_destination = subprocess.PIPE
43       if logging.getLogger().isEnabledFor(logging.DEBUG):
44         stderror_destination = None
45
46       self._powermetrics_process = subprocess.Popen(args,
47           stdout=subprocess.PIPE, stderr=stderror_destination)
48
49     def StopMonitoringPowerAsync(self):
50       assert self._powermetrics_process, (
51           "StartMonitoringPowerAsync() not called.")
52       # Tell powermetrics to take an immediate sample.
53       try:
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()
60       finally:
61         self._powermetrics_output_file.close()
62         self._powermetrics_output_file = None
63         self._powermetrics_process = None
64
65   def __init__(self):
66     super(MacPlatformBackend, self).__init__()
67     self.libproc = None
68     self.powermetrics_tool_ = MacPlatformBackend.PowerMetricsUtility()
69
70   def StartRawDisplayFrameRateMeasurement(self):
71     raise NotImplementedError()
72
73   def StopRawDisplayFrameRateMeasurement(self):
74     raise NotImplementedError()
75
76   def GetRawDisplayFrameRateMeasurements(self):
77     raise NotImplementedError()
78
79   def IsThermallyThrottled(self):
80     raise NotImplementedError()
81
82   def HasBeenThermallyThrottled(self):
83     raise NotImplementedError()
84
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)]
107       PROC_PIDTASKINFO = 4
108       def __init__(self):
109         self.size = ctypes.sizeof(self)
110         super(ProcTaskInfo, self).__init__()
111
112     proc_info = ProcTaskInfo()
113     if not self.libproc:
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)
117
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}
122
123   def GetCpuTimestamp(self):
124     """Return current timestamp in seconds."""
125     return {'TotalTime': time.time()}
126
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
134     return 0
135
136   def PurgeUnpinnedMemory(self):
137     # TODO(pliard): Implement this.
138     pass
139
140   def GetMemoryStats(self, pid):
141     rss_vsz = self._GetPsOutput(['rss', 'vsz'], pid)
142     if rss_vsz:
143       rss, vsz = rss_vsz[0].split()
144       return {'VM': 1024 * int(vsz),
145               'WorkingSetSize': 1024 * int(rss)}
146     return {}
147
148   def GetOSName(self):
149     return 'mac'
150
151   def GetOSVersionName(self):
152     os_version = os.uname()[2]
153
154     if os_version.startswith('9.'):
155       return 'leopard'
156     if os_version.startswith('10.'):
157       return 'snowleopard'
158     if os_version.startswith('11.'):
159       return 'lion'
160     if os_version.startswith('12.'):
161       return 'mountainlion'
162     if os_version.startswith('13.'):
163       return 'mavericks'
164
165     raise NotImplementedError("Unknown OS X version %s." % os_version)
166
167   def CanFlushIndividualFilesFromSystemCache(self):
168     return False
169
170   def FlushEntireSystemCache(self):
171     p = subprocess.Popen(['purge'])
172     p.wait()
173     assert p.returncode == 0, 'Failed to flush system cache'
174
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)
180
181   def SetPowerMetricsUtilityForTest(self, obj):
182     self.powermetrics_tool_ = obj
183
184   def StartMonitoringPowerAsync(self):
185     self.powermetrics_tool_.StartMonitoringPowerAsync()
186
187   def _ParsePowerMetricsOutput(self, powermetrics_output):
188     """Parse output of powermetrics command line utility.
189
190     Returns:
191         Dictionary in the format returned by StopMonitoringPowerAsync().
192     """
193     power_samples = []
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)
199
200       # Duration of this sample.
201       sample_duration_ms = int(plist['elapsed_ns']) / 10**6
202
203       if 'processor' not in plist:
204         continue
205       processor = plist['processor']
206
207       energy_consumption_mw = int(processor.get('package_watts', 0)) * 10**3
208
209       total_energy_consumption_mwh += (energy_consumption_mw *
210           (sample_duration_ms / 3600000.))
211
212       power_samples.append(energy_consumption_mw)
213
214     # -------- Collect and Process Data -------------
215     out_dict = {}
216     # Raw power usage samples.
217     if power_samples:
218       out_dict['power_samples_mw'] = power_samples
219       out_dict['energy_consumption_mwh'] = total_energy_consumption_mwh
220
221     return out_dict
222
223   def StopMonitoringPowerAsync(self):
224     powermetrics_output = self.powermetrics_tool_.StopMonitoringPowerAsync()
225     assert len(powermetrics_output) > 0
226     return self._ParsePowerMetricsOutput(powermetrics_output)