• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 Initialize(self):
105    pass
106
107  def CanMonitorThermalThrottling(self):
108    """Platforms may be able to detect thermal throttling.
109
110    Some fan-less computers go into a reduced performance mode when their heat
111    exceeds a certain threshold. Performance tests in particular should use this
112    API to detect if this has happened and interpret results accordingly.
113    """
114    return self._platform_backend.CanMonitorThermalThrottling()
115
116  def GetSystemLog(self):
117    return self._platform_backend.GetSystemLog()
118
119  def IsThermallyThrottled(self):
120    """Returns True if the device is currently thermally throttled."""
121    return self._platform_backend.IsThermallyThrottled()
122
123  def HasBeenThermallyThrottled(self):
124    """Returns True if the device has been thermally throttled."""
125    return self._platform_backend.HasBeenThermallyThrottled()
126
127  def GetDeviceTypeName(self):
128    """Returns a string description of the Platform device, or None.
129
130    Examples: Nexus 7, Nexus 6, Desktop"""
131    return self._platform_backend.GetDeviceTypeName()
132
133  def GetArchName(self):
134    """Returns a string description of the Platform architecture.
135
136    Examples: x86_64 (posix), AMD64 (win), armeabi-v7a, x86"""
137    return self._platform_backend.GetArchName()
138
139  def GetOSName(self):
140    """Returns a string description of the Platform OS.
141
142    Examples: WIN, MAC, LINUX, CHROMEOS"""
143    return self._platform_backend.GetOSName()
144
145  def GetOSVersionName(self):
146    """Returns a logically sortable, string-like description of the Platform OS
147    version.
148
149    Examples: VISTA, WIN7, LION, MOUNTAINLION"""
150    return self._platform_backend.GetOSVersionName()
151
152  def GetOSVersionNumber(self):
153    """Returns an integer description of the Platform OS major version.
154
155    Examples: On Mac, 13 for Mavericks, 14 for Yosemite."""
156    return self._platform_backend.GetOSVersionNumber()
157
158  def GetSystemTotalPhysicalMemory(self):
159    """Returns an integer with the total physical memory in bytes."""
160    return self._platform_backend.GetSystemTotalPhysicalMemory()
161
162  def CanFlushIndividualFilesFromSystemCache(self):
163    """Returns true if the disk cache can be flushed for specific files."""
164    return self._platform_backend.CanFlushIndividualFilesFromSystemCache()
165
166  def SupportFlushEntireSystemCache(self):
167    """Returns true if entire system cache can be flushed.
168
169    Also checks that platform has required privilegues to flush system caches.
170    """
171    return self._platform_backend.SupportFlushEntireSystemCache()
172
173  def FlushEntireSystemCache(self):
174    """Flushes the OS's file cache completely.
175
176    This function may require root or administrator access. Clients should
177    call SupportFlushEntireSystemCache to check first.
178    """
179    return self._platform_backend.FlushEntireSystemCache()
180
181  def FlushSystemCacheForDirectory(self, directory):
182    """Flushes the OS's file cache for the specified directory.
183
184    This function does not require root or administrator access."""
185    return self._platform_backend.FlushSystemCacheForDirectory(directory)
186
187  def FlushDnsCache(self):
188    """Flushes the OS's DNS cache completely.
189
190    This function may require root or administrator access."""
191    return self._platform_backend.FlushDnsCache()
192
193  def LaunchApplication(self,
194                        application,
195                        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,
206        parameters,
207        elevate_privilege=elevate_privilege)
208
209  def IsApplicationRunning(self, application):
210    """Returns whether an application is currently running."""
211    return self._platform_backend.IsApplicationRunning(application)
212
213  def CanLaunchApplication(self, application):
214    """Returns whether the platform can launch the given application."""
215    return self._platform_backend.CanLaunchApplication(application)
216
217  def InstallApplication(self, application):
218    """Installs the given application."""
219    return self._platform_backend.InstallApplication(application)
220
221  def CanCaptureVideo(self):
222    """Returns a bool indicating whether the platform supports video capture."""
223    return self._platform_backend.CanCaptureVideo()
224
225  def StartVideoCapture(self, min_bitrate_mbps):
226    """Starts capturing video.
227
228    Outer framing may be included (from the OS, browser window, and webcam).
229
230    Args:
231      min_bitrate_mbps: The minimum capture bitrate in MegaBits Per Second.
232          The platform is free to deliver a higher bitrate if it can do so
233          without increasing overhead.
234
235    Raises:
236      ValueError if the required |min_bitrate_mbps| can't be achieved.
237    """
238    return self._platform_backend.StartVideoCapture(min_bitrate_mbps)
239
240  def StopVideoCapture(self):
241    """Stops capturing video.
242
243    Returns:
244      A telemetry.core.video.Video object.
245    """
246    return self._platform_backend.StopVideoCapture()
247
248  def CanMonitorPower(self):
249    """Returns True iff power can be monitored asynchronously via
250    StartMonitoringPower() and StopMonitoringPower().
251    """
252    return self._platform_backend.CanMonitorPower()
253
254  def CanMeasurePerApplicationPower(self):
255    """Returns True if the power monitor can measure power for the target
256    application in isolation. False if power measurement is for full system
257    energy consumption."""
258    return self._platform_backend.CanMeasurePerApplicationPower()
259
260  def StartMonitoringPower(self, browser):
261    """Starts monitoring power utilization statistics.
262
263    Args:
264      browser: The browser to monitor.
265    """
266    assert self._platform_backend.CanMonitorPower()
267    self._platform_backend.StartMonitoringPower(browser)
268    self._is_monitoring_power = True
269
270  def StopMonitoringPower(self):
271    """Stops monitoring power utilization and returns stats
272
273    Returns:
274      None if power measurement failed for some reason, otherwise a dict of
275      power utilization statistics containing: {
276        # An identifier for the data provider. Allows to evaluate the precision
277        # of the data. Example values: monsoon, powermetrics, ds2784
278        'identifier': identifier,
279
280        # The instantaneous power (voltage * current) reading in milliwatts at
281        # each sample.
282        'power_samples_mw':  [mw0, mw1, ..., mwN],
283
284        # The full system energy consumption during the sampling period in
285        # milliwatt hours. May be estimated by integrating power samples or may
286        # be exact on supported hardware.
287        'energy_consumption_mwh': mwh,
288
289        # The target application's energy consumption during the sampling period
290        # in milliwatt hours. Should be returned iff
291        # CanMeasurePerApplicationPower() return true.
292        'application_energy_consumption_mwh': mwh,
293
294        # A platform-specific dictionary of additional details about the
295        # utilization of individual hardware components.
296        component_utilization: {
297          ...
298        }
299        # Platform-specific data not attributed to any particular hardware
300        # component.
301        platform_info: {
302
303          # Device-specific onboard temperature sensor.
304          'average_temperature_c': c,
305
306           ...
307        }
308
309      }
310    """
311    ret_val = self._platform_backend.StopMonitoringPower()
312    self._is_monitoring_power = False
313    return ret_val
314
315  def IsMonitoringPower(self):
316    """Returns true if power is currently being monitored, false otherwise."""
317    # TODO(rnephew): Remove when crbug.com/553601 is solved.
318    real_logging.info('IsMonitoringPower: %s', self._is_monitoring_power)
319    return self._is_monitoring_power
320
321  def CanMonitorNetworkData(self):
322    """Returns true if network data can be retrieved, false otherwise."""
323    return self._platform_backend.CanMonitorNetworkData()
324
325  def GetNetworkData(self, browser):
326    """Get current network data.
327    Returns:
328      Tuple of (sent_data, received_data) in kb if data can be found,
329      None otherwise.
330    """
331    assert browser.platform == self
332    return self._platform_backend.GetNetworkData(browser)
333
334  def IsCooperativeShutdownSupported(self):
335    """Indicates whether CooperativelyShutdown, below, is supported.
336    It is not necessary to implement it on all platforms."""
337    return self._platform_backend.IsCooperativeShutdownSupported()
338
339  def CooperativelyShutdown(self, proc, app_name):
340    """Cooperatively shut down the given process from subprocess.Popen.
341
342    Currently this is only implemented on Windows. See
343    crbug.com/424024 for background on why it was added.
344
345    Args:
346      proc: a process object returned from subprocess.Popen.
347      app_name: on Windows, is the prefix of the application's window
348          class name that should be searched for. This helps ensure
349          that only the application's windows are closed.
350
351    Returns True if it is believed the attempt succeeded.
352    """
353    return self._platform_backend.CooperativelyShutdown(proc, app_name)
354
355  def CanTakeScreenshot(self):
356    return self._platform_backend.CanTakeScreenshot()
357
358  # TODO(nednguyen): Implement this on Mac, Linux & Win. (crbug.com/369490)
359  def TakeScreenshot(self, file_path):
360    """ Takes a screenshot of the platform and save to |file_path|.
361
362    Note that this method may not be supported on all platform, so check with
363    CanTakeScreenshot before calling this.
364
365    Args:
366      file_path: Where to save the screenshot to. If the platform is remote,
367        |file_path| is the path on the host platform.
368
369    Returns True if it is believed the attempt succeeded.
370    """
371    return self._platform_backend.TakeScreenshot(file_path)
372
373  def StartLocalServer(self, server):
374    """Starts a LocalServer and associates it with this platform.
375    |server.Close()| should be called manually to close the started server.
376    """
377    self._local_server_controller.StartServer(server)
378
379  @property
380  def http_server(self):
381    return self._local_server_controller.GetRunningServer(
382        memory_cache_http_server.MemoryCacheHTTPServer, None)
383
384  def SetHTTPServerDirectories(self, paths):
385    """Returns True if the HTTP server was started, False otherwise."""
386    if isinstance(paths, basestring):
387      paths = set([paths])
388    paths = set(os.path.realpath(p) for p in paths)
389
390    # If any path is in a subdirectory of another, remove the subdirectory.
391    duplicates = set()
392    for parent_path in paths:
393      for sub_path in paths:
394        if parent_path == sub_path:
395          continue
396        if os.path.commonprefix((parent_path, sub_path)) == parent_path:
397          duplicates.add(sub_path)
398    paths -= duplicates
399
400    if self.http_server:
401      if paths and self.http_server.paths == paths:
402        return False
403
404      self.http_server.Close()
405
406    if not paths:
407      return False
408
409    server = memory_cache_http_server.MemoryCacheHTTPServer(paths)
410    self.StartLocalServer(server)
411    return True
412
413  def StopAllLocalServers(self):
414    self._local_server_controller.Close()
415
416  @property
417  def local_servers(self):
418    """Returns the currently running local servers."""
419    return self._local_server_controller.local_servers
420
421  def HasBattOrConnected(self):
422    return  self._platform_backend.HasBattOrConnected()
423
424  def WaitForTemperature(self, temp):
425    return self._platform_backend.WaitForTemperature(temp)
426