1# Copyright 2012 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
5import logging as real_logging
6import os
7import sys
8
9from telemetry.core import discover
10from telemetry.core import util
11from telemetry.core.platform import platform_backend as platform_backend_module
12from telemetry.core.platform import profiling_controller
13from telemetry.core.platform import tracing_controller
14
15
16_host_platform = None
17# Remote platform is a dictionary from device ids to remote platform instances.
18_remote_platforms = {}
19
20
21def _IsRunningOnCrosDevice():
22  """Returns True if we're on a ChromeOS device."""
23  lsb_release = '/etc/lsb-release'
24  if sys.platform.startswith('linux') and os.path.exists(lsb_release):
25    with open(lsb_release, 'r') as f:
26      res = f.read()
27      if res.count('CHROMEOS_RELEASE_NAME'):
28        return True
29  return False
30
31
32def _InitHostPlatformIfNeeded():
33  global _host_platform
34  if _host_platform:
35    return
36  if _IsRunningOnCrosDevice():
37    from telemetry.core.platform import cros_platform_backend
38    backend = cros_platform_backend.CrosPlatformBackend()
39  elif sys.platform.startswith('linux'):
40    from telemetry.core.platform import linux_platform_backend
41    backend = linux_platform_backend.LinuxPlatformBackend()
42  elif sys.platform == 'darwin':
43    from telemetry.core.platform import mac_platform_backend
44    backend = mac_platform_backend.MacPlatformBackend()
45  elif sys.platform == 'win32':
46    from telemetry.core.platform import win_platform_backend
47    backend = win_platform_backend.WinPlatformBackend()
48  else:
49    raise NotImplementedError()
50
51  _host_platform = Platform(backend)
52
53
54def GetHostPlatform():
55  _InitHostPlatformIfNeeded()
56  return _host_platform
57
58
59def GetPlatformForDevice(device, logging=real_logging):
60  """ Returns a platform instance for the device.
61    Args:
62      device: a device.Device instance.
63  """
64  if device.guid in _remote_platforms:
65    return _remote_platforms[device.guid]
66  try:
67    platform_backend = None
68    platform_dir = os.path.dirname(os.path.realpath(__file__))
69    for platform_backend_class in discover.DiscoverClasses(
70        platform_dir, util.GetTelemetryDir(),
71        platform_backend_module.PlatformBackend).itervalues():
72      if platform_backend_class.SupportsDevice(device):
73        platform_backend = platform_backend_class(device)
74        _remote_platforms[device.guid] = Platform(platform_backend)
75        return _remote_platforms[device.guid]
76    return None
77  except Exception:
78    logging.error('Fail to create platform instance for %s.', device.name)
79    raise
80
81
82class Platform(object):
83  """The platform that the target browser is running on.
84
85  Provides a limited interface to interact with the platform itself, where
86  possible. It's important to note that platforms may not provide a specific
87  API, so check with IsFooBar() for availability.
88  """
89  def __init__(self, platform_backend):
90    self._platform_backend = platform_backend
91    self._platform_backend.SetPlatform(self)
92    self._tracing_controller = tracing_controller.TracingController(
93        self._platform_backend.tracing_controller_backend)
94    self._profiling_controller = profiling_controller.ProfilingController(
95        self._platform_backend.profiling_controller_backend)
96
97  @property
98  def tracing_controller(self):
99    return self._tracing_controller
100
101  @property
102  def profiling_controller(self):
103    return self._profiling_controller
104
105  def IsRawDisplayFrameRateSupported(self):
106    """Platforms may be able to collect GL surface stats."""
107    return self._platform_backend.IsRawDisplayFrameRateSupported()
108
109  def StartRawDisplayFrameRateMeasurement(self):
110    """Start measuring GL surface stats."""
111    return self._platform_backend.StartRawDisplayFrameRateMeasurement()
112
113  def StopRawDisplayFrameRateMeasurement(self):
114    """Stop measuring GL surface stats."""
115    return self._platform_backend.StopRawDisplayFrameRateMeasurement()
116
117  class RawDisplayFrameRateMeasurement(object):
118    def __init__(self, name, value, unit):
119      self._name = name
120      self._value = value
121      self._unit = unit
122
123    @property
124    def name(self):
125      return self._name
126
127    @property
128    def value(self):
129      return self._value
130
131    @property
132    def unit(self):
133      return self._unit
134
135  def GetRawDisplayFrameRateMeasurements(self):
136    """Returns a list of RawDisplayFrameRateMeasurement."""
137    return self._platform_backend.GetRawDisplayFrameRateMeasurements()
138
139  def CanMonitorThermalThrottling(self):
140    """Platforms may be able to detect thermal throttling.
141
142    Some fan-less computers go into a reduced performance mode when their heat
143    exceeds a certain threshold. Performance tests in particular should use this
144    API to detect if this has happened and interpret results accordingly.
145    """
146    return self._platform_backend.CanMonitorThermalThrottling()
147
148  def IsThermallyThrottled(self):
149    """Returns True if the device is currently thermally throttled."""
150    return self._platform_backend.IsThermallyThrottled()
151
152  def HasBeenThermallyThrottled(self):
153    """Returns True if the device has been thermally throttled."""
154    return self._platform_backend.HasBeenThermallyThrottled()
155
156  def GetOSName(self):
157    """Returns a string description of the Platform OS.
158
159    Examples: WIN, MAC, LINUX, CHROMEOS"""
160    return self._platform_backend.GetOSName()
161
162  def GetOSVersionName(self):
163    """Returns a logically sortable, string-like description of the Platform OS
164    version.
165
166    Examples: VISTA, WIN7, LION, MOUNTAINLION"""
167    return self._platform_backend.GetOSVersionName()
168
169  def CanFlushIndividualFilesFromSystemCache(self):
170    """Returns true if the disk cache can be flushed for specific files."""
171    return self._platform_backend.CanFlushIndividualFilesFromSystemCache()
172
173  def FlushEntireSystemCache(self):
174    """Flushes the OS's file cache completely.
175
176    This function may require root or administrator access."""
177    return self._platform_backend.FlushEntireSystemCache()
178
179  def FlushSystemCacheForDirectory(self, directory, ignoring=None):
180    """Flushes the OS's file cache for the specified directory.
181
182    Any files or directories inside |directory| matching a name in the
183    |ignoring| list will be skipped.
184
185    This function does not require root or administrator access."""
186    return self._platform_backend.FlushSystemCacheForDirectory(
187        directory, ignoring=ignoring)
188
189  def FlushDnsCache(self):
190    """Flushes the OS's DNS cache completely.
191
192    This function may require root or administrator access."""
193    return self._platform_backend.FlushDnsCache()
194
195  def LaunchApplication(self, application, parameters=None,
196                        elevate_privilege=False):
197    """"Launches the given |application| with a list of |parameters| on the OS.
198
199    Set |elevate_privilege| to launch the application with root or admin rights.
200
201    Returns:
202      A popen style process handle for host platforms.
203    """
204    return self._platform_backend.LaunchApplication(
205        application, parameters, elevate_privilege=elevate_privilege)
206
207  def IsApplicationRunning(self, application):
208    """Returns whether an application is currently running."""
209    return self._platform_backend.IsApplicationRunning(application)
210
211  def CanLaunchApplication(self, application):
212    """Returns whether the platform can launch the given application."""
213    return self._platform_backend.CanLaunchApplication(application)
214
215  def InstallApplication(self, application):
216    """Installs the given application."""
217    return self._platform_backend.InstallApplication(application)
218
219  def CanCaptureVideo(self):
220    """Returns a bool indicating whether the platform supports video capture."""
221    return self._platform_backend.CanCaptureVideo()
222
223  def StartVideoCapture(self, min_bitrate_mbps):
224    """Starts capturing video.
225
226    Outer framing may be included (from the OS, browser window, and webcam).
227
228    Args:
229      min_bitrate_mbps: The minimum capture bitrate in MegaBits Per Second.
230          The platform is free to deliver a higher bitrate if it can do so
231          without increasing overhead.
232
233    Raises:
234      ValueError if the required |min_bitrate_mbps| can't be achieved.
235    """
236    return self._platform_backend.StartVideoCapture(min_bitrate_mbps)
237
238  def StopVideoCapture(self):
239    """Stops capturing video.
240
241    Returns:
242      A telemetry.core.video.Video object.
243    """
244    return self._platform_backend.StopVideoCapture()
245
246  def CanMonitorPower(self):
247    """Returns True iff power can be monitored asynchronously via
248    StartMonitoringPower() and StopMonitoringPower().
249    """
250    return self._platform_backend.CanMonitorPower()
251
252  def CanMeasurePerApplicationPower(self):
253    """Returns True if the power monitor can measure power for the target
254    application in isolation. False if power measurement is for full system
255    energy consumption."""
256    return self._platform_backend.CanMeasurePerApplicationPower()
257
258
259  def StartMonitoringPower(self, browser):
260    """Starts monitoring power utilization statistics.
261
262    Args:
263      browser: The browser to monitor.
264    """
265    assert self._platform_backend.CanMonitorPower()
266    self._platform_backend.StartMonitoringPower(browser)
267
268  def StopMonitoringPower(self):
269    """Stops monitoring power utilization and returns stats
270
271    Returns:
272      None if power measurement failed for some reason, otherwise a dict of
273      power utilization statistics containing: {
274        # An identifier for the data provider. Allows to evaluate the precision
275        # of the data. Example values: monsoon, powermetrics, ds2784
276        'identifier': identifier,
277
278        # The instantaneous power (voltage * current) reading in milliwatts at
279        # each sample.
280        'power_samples_mw':  [mw0, mw1, ..., mwN],
281
282        # The full system energy consumption during the sampling period in
283        # milliwatt hours. May be estimated by integrating power samples or may
284        # be exact on supported hardware.
285        'energy_consumption_mwh': mwh,
286
287        # The target application's energy consumption during the sampling period
288        # in milliwatt hours. Should be returned iff
289        # CanMeasurePerApplicationPower() return true.
290        'application_energy_consumption_mwh': mwh,
291
292        # A platform-specific dictionary of additional details about the
293        # utilization of individual hardware components.
294        component_utilization: {
295
296          # Platform-specific data not attributed to any particular hardware
297          # component.
298          whole_package: {
299
300            # Device-specific onboard temperature sensor.
301            'average_temperature_c': c,
302
303            ...
304          }
305
306          ...
307        }
308      }
309    """
310    return self._platform_backend.StopMonitoringPower()
311