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