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