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.
4import logging as real_logging
5import os
6import sys
7
8from telemetry.core import discover
9from telemetry.core import local_server
10from telemetry.core import memory_cache_http_server
11from telemetry.core import network_controller
12from telemetry.core import tracing_controller
13from telemetry.core import util
14from telemetry.internal.platform import (platform_backend as
15                                         platform_backend_module)
16
17_host_platform = None
18# Remote platform is a dictionary from device ids to remote platform instances.
19_remote_platforms = {}
20
21
22def _InitHostPlatformIfNeeded():
23  global _host_platform
24  if _host_platform:
25    return
26  backend = None
27  backends = _IterAllPlatformBackendClasses()
28  for platform_backend_class in backends:
29    if platform_backend_class.IsPlatformBackendForHost():
30      backend = platform_backend_class()
31      break
32  if not backend:
33    raise NotImplementedError()
34  _host_platform = Platform(backend)
35
36
37def GetHostPlatform():
38  _InitHostPlatformIfNeeded()
39  return _host_platform
40
41
42def _IterAllPlatformBackendClasses():
43  platform_dir = os.path.dirname(os.path.realpath(
44      platform_backend_module.__file__))
45  return discover.DiscoverClasses(
46      platform_dir, util.GetTelemetryDir(),
47      platform_backend_module.PlatformBackend).itervalues()
48
49
50def GetPlatformForDevice(device, finder_options, logging=real_logging):
51  """ Returns a platform instance for the device.
52    Args:
53      device: a device.Device instance.
54  """
55  if device.guid in _remote_platforms:
56    return _remote_platforms[device.guid]
57  try:
58    for platform_backend_class in _IterAllPlatformBackendClasses():
59      if platform_backend_class.SupportsDevice(device):
60        _remote_platforms[device.guid] = (
61            platform_backend_class.CreatePlatformForDevice(device,
62                                                           finder_options))
63        return _remote_platforms[device.guid]
64    return None
65  except Exception:
66    current_exception = sys.exc_info()
67    logging.error('Fail to create platform instance for %s.', device.name)
68    raise current_exception[0], current_exception[1], current_exception[2]
69
70
71class Platform(object):
72  """The platform that the target browser is running on.
73
74  Provides a limited interface to interact with the platform itself, where
75  possible. It's important to note that platforms may not provide a specific
76  API, so check with IsFooBar() for availability.
77  """
78
79  def __init__(self, platform_backend):
80    self._platform_backend = platform_backend
81    self._platform_backend.InitPlatformBackend()
82    self._platform_backend.SetPlatform(self)
83    self._network_controller = network_controller.NetworkController(
84        self._platform_backend.network_controller_backend)
85    self._tracing_controller = tracing_controller.TracingController(
86        self._platform_backend.tracing_controller_backend)
87    self._local_server_controller = local_server.LocalServerController(
88        self._platform_backend)
89    self._is_monitoring_power = False
90
91  @property
92  def is_host_platform(self):
93    return self == GetHostPlatform()
94
95  @property
96  def network_controller(self):
97    """Control network settings and servers to simulate the Web."""
98    return self._network_controller
99
100  @property
101  def tracing_controller(self):
102    return self._tracing_controller
103
104  def CanMonitorThermalThrottling(self):
105    """Platforms may be able to detect thermal throttling.
106
107    Some fan-less computers go into a reduced performance mode when their heat
108    exceeds a certain threshold. Performance tests in particular should use this
109    API to detect if this has happened and interpret results accordingly.
110    """
111    return self._platform_backend.CanMonitorThermalThrottling()
112
113  def IsThermallyThrottled(self):
114    """Returns True if the device is currently thermally throttled."""
115    return self._platform_backend.IsThermallyThrottled()
116
117  def HasBeenThermallyThrottled(self):
118    """Returns True if the device has been thermally throttled."""
119    return self._platform_backend.HasBeenThermallyThrottled()
120
121  def GetDeviceTypeName(self):
122    """Returns a string description of the Platform device, or None.
123
124    Examples: Nexus 7, Nexus 6, Desktop"""
125    return self._platform_backend.GetDeviceTypeName()
126
127  def GetArchName(self):
128    """Returns a string description of the Platform architecture.
129
130    Examples: x86_64 (posix), AMD64 (win), armeabi-v7a, x86"""
131    return self._platform_backend.GetArchName()
132
133  def GetOSName(self):
134    """Returns a string description of the Platform OS.
135
136    Examples: WIN, MAC, LINUX, CHROMEOS"""
137    return self._platform_backend.GetOSName()
138
139  def GetOSVersionName(self):
140    """Returns a logically sortable, string-like description of the Platform OS
141    version.
142
143    Examples: VISTA, WIN7, LION, MOUNTAINLION"""
144    return self._platform_backend.GetOSVersionName()
145
146  def GetOSVersionNumber(self):
147    """Returns an integer description of the Platform OS major version.
148
149    Examples: On Mac, 13 for Mavericks, 14 for Yosemite."""
150    return self._platform_backend.GetOSVersionNumber()
151
152  def CanFlushIndividualFilesFromSystemCache(self):
153    """Returns true if the disk cache can be flushed for specific files."""
154    return self._platform_backend.CanFlushIndividualFilesFromSystemCache()
155
156  def FlushEntireSystemCache(self):
157    """Flushes the OS's file cache completely.
158
159    This function may require root or administrator access."""
160    return self._platform_backend.FlushEntireSystemCache()
161
162  def FlushSystemCacheForDirectory(self, directory):
163    """Flushes the OS's file cache for the specified directory.
164
165    This function does not require root or administrator access."""
166    return self._platform_backend.FlushSystemCacheForDirectory(directory)
167
168  def FlushDnsCache(self):
169    """Flushes the OS's DNS cache completely.
170
171    This function may require root or administrator access."""
172    return self._platform_backend.FlushDnsCache()
173
174  def LaunchApplication(self,
175                        application,
176                        parameters=None,
177                        elevate_privilege=False):
178    """"Launches the given |application| with a list of |parameters| on the OS.
179
180    Set |elevate_privilege| to launch the application with root or admin rights.
181
182    Returns:
183      A popen style process handle for host platforms.
184    """
185    return self._platform_backend.LaunchApplication(
186        application,
187        parameters,
188        elevate_privilege=elevate_privilege)
189
190  def IsApplicationRunning(self, application):
191    """Returns whether an application is currently running."""
192    return self._platform_backend.IsApplicationRunning(application)
193
194  def CanLaunchApplication(self, application):
195    """Returns whether the platform can launch the given application."""
196    return self._platform_backend.CanLaunchApplication(application)
197
198  def InstallApplication(self, application):
199    """Installs the given application."""
200    return self._platform_backend.InstallApplication(application)
201
202  def CanCaptureVideo(self):
203    """Returns a bool indicating whether the platform supports video capture."""
204    return self._platform_backend.CanCaptureVideo()
205
206  def StartVideoCapture(self, min_bitrate_mbps):
207    """Starts capturing video.
208
209    Outer framing may be included (from the OS, browser window, and webcam).
210
211    Args:
212      min_bitrate_mbps: The minimum capture bitrate in MegaBits Per Second.
213          The platform is free to deliver a higher bitrate if it can do so
214          without increasing overhead.
215
216    Raises:
217      ValueError if the required |min_bitrate_mbps| can't be achieved.
218    """
219    return self._platform_backend.StartVideoCapture(min_bitrate_mbps)
220
221  def StopVideoCapture(self):
222    """Stops capturing video.
223
224    Returns:
225      A telemetry.core.video.Video object.
226    """
227    return self._platform_backend.StopVideoCapture()
228
229  def CanMonitorPower(self):
230    """Returns True iff power can be monitored asynchronously via
231    StartMonitoringPower() and StopMonitoringPower().
232    """
233    return self._platform_backend.CanMonitorPower()
234
235  def CanMeasurePerApplicationPower(self):
236    """Returns True if the power monitor can measure power for the target
237    application in isolation. False if power measurement is for full system
238    energy consumption."""
239    return self._platform_backend.CanMeasurePerApplicationPower()
240
241  def StartMonitoringPower(self, browser):
242    """Starts monitoring power utilization statistics.
243
244    Args:
245      browser: The browser to monitor.
246    """
247    assert self._platform_backend.CanMonitorPower()
248    self._platform_backend.StartMonitoringPower(browser)
249    self._is_monitoring_power = True
250
251  def StopMonitoringPower(self):
252    """Stops monitoring power utilization and returns stats
253
254    Returns:
255      None if power measurement failed for some reason, otherwise a dict of
256      power utilization statistics containing: {
257        # An identifier for the data provider. Allows to evaluate the precision
258        # of the data. Example values: monsoon, powermetrics, ds2784
259        'identifier': identifier,
260
261        # The instantaneous power (voltage * current) reading in milliwatts at
262        # each sample.
263        'power_samples_mw':  [mw0, mw1, ..., mwN],
264
265        # The full system energy consumption during the sampling period in
266        # milliwatt hours. May be estimated by integrating power samples or may
267        # be exact on supported hardware.
268        'energy_consumption_mwh': mwh,
269
270        # The target application's energy consumption during the sampling period
271        # in milliwatt hours. Should be returned iff
272        # CanMeasurePerApplicationPower() return true.
273        'application_energy_consumption_mwh': mwh,
274
275        # A platform-specific dictionary of additional details about the
276        # utilization of individual hardware components.
277        component_utilization: {
278          ...
279        }
280        # Platform-specific data not attributed to any particular hardware
281        # component.
282        platform_info: {
283
284          # Device-specific onboard temperature sensor.
285          'average_temperature_c': c,
286
287           ...
288        }
289
290      }
291    """
292    ret_val = self._platform_backend.StopMonitoringPower()
293    self._is_monitoring_power = False
294    return ret_val
295
296  def IsMonitoringPower(self):
297    """Returns true if power is currently being monitored, false otherwise."""
298    # TODO(rnephew): Remove when crbug.com/553601 is solved.
299    real_logging.info('IsMonitoringPower: %s', self._is_monitoring_power)
300    return self._is_monitoring_power
301
302  def CanMonitorNetworkData(self):
303    """Returns true if network data can be retrieved, false otherwise."""
304    return self._platform_backend.CanMonitorNetworkData()
305
306  def GetNetworkData(self, browser):
307    """Get current network data.
308    Returns:
309      Tuple of (sent_data, received_data) in kb if data can be found,
310      None otherwise.
311    """
312    assert browser.platform == self
313    return self._platform_backend.GetNetworkData(browser)
314
315  def IsCooperativeShutdownSupported(self):
316    """Indicates whether CooperativelyShutdown, below, is supported.
317    It is not necessary to implement it on all platforms."""
318    return self._platform_backend.IsCooperativeShutdownSupported()
319
320  def CooperativelyShutdown(self, proc, app_name):
321    """Cooperatively shut down the given process from subprocess.Popen.
322
323    Currently this is only implemented on Windows. See
324    crbug.com/424024 for background on why it was added.
325
326    Args:
327      proc: a process object returned from subprocess.Popen.
328      app_name: on Windows, is the prefix of the application's window
329          class name that should be searched for. This helps ensure
330          that only the application's windows are closed.
331
332    Returns True if it is believed the attempt succeeded.
333    """
334    return self._platform_backend.CooperativelyShutdown(proc, app_name)
335
336  def CanTakeScreenshot(self):
337    return self._platform_backend.CanTakeScreenshot()
338
339  # TODO(nednguyen): Implement this on Mac, Linux & Win. (crbug.com/369490)
340  def TakeScreenshot(self, file_path):
341    """ Takes a screenshot of the platform and save to |file_path|.
342
343    Note that this method may not be supported on all platform, so check with
344    CanTakeScreenshot before calling this.
345
346    Args:
347      file_path: Where to save the screenshot to. If the platform is remote,
348        |file_path| is the path on the host platform.
349
350    Returns True if it is believed the attempt succeeded.
351    """
352    return self._platform_backend.TakeScreenshot(file_path)
353
354  def StartLocalServer(self, server):
355    """Starts a LocalServer and associates it with this platform.
356    |server.Close()| should be called manually to close the started server.
357    """
358    self._local_server_controller.StartServer(server)
359
360  @property
361  def http_server(self):
362    return self._local_server_controller.GetRunningServer(
363        memory_cache_http_server.MemoryCacheHTTPServer, None)
364
365  def SetHTTPServerDirectories(self, paths):
366    """Returns True if the HTTP server was started, False otherwise."""
367    if isinstance(paths, basestring):
368      paths = set([paths])
369    paths = set(os.path.realpath(p) for p in paths)
370
371    # If any path is in a subdirectory of another, remove the subdirectory.
372    duplicates = set()
373    for parent_path in paths:
374      for sub_path in paths:
375        if parent_path == sub_path:
376          continue
377        if os.path.commonprefix((parent_path, sub_path)) == parent_path:
378          duplicates.add(sub_path)
379    paths -= duplicates
380
381    if self.http_server:
382      if paths and self.http_server.paths == paths:
383        return False
384
385      self.http_server.Close()
386
387    if not paths:
388      return False
389
390    server = memory_cache_http_server.MemoryCacheHTTPServer(paths)
391    self.StartLocalServer(server)
392    return True
393
394  def StopAllLocalServers(self):
395    self._local_server_controller.Close()
396
397  @property
398  def local_servers(self):
399    """Returns the currently running local servers."""
400    return self._local_server_controller.local_servers
401