1# Copyright (C) 2012 Google Inc. All rights reserved. 2# 3# Redistribution and use in source and binary forms, with or without 4# modification, are permitted provided that the following conditions are 5# met: 6# 7# * Redistributions of source code must retain the above copyright 8# notice, this list of conditions and the following disclaimer. 9# * Redistributions in binary form must reproduce the above 10# copyright notice, this list of conditions and the following disclaimer 11# in the documentation and/or other materials provided with the 12# distribution. 13# * Neither the name of Google Inc. nor the names of its 14# contributors may be used to endorse or promote products derived from 15# this software without specific prior written permission. 16# 17# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 29import itertools 30import logging 31import os 32import posixpath 33import re 34import sys 35import threading 36import time 37 38from blinkpy.common import exit_codes 39from blinkpy.common.path_finder import RELATIVE_WEB_TESTS 40from blinkpy.common.path_finder import WEB_TESTS_LAST_COMPONENT 41from blinkpy.common.path_finder import get_chromium_src_dir 42from blinkpy.common.system.executive import ScriptError 43from blinkpy.common.system.profiler import SingleFileOutputProfiler 44from blinkpy.web_tests.breakpad.dump_reader_multipart import DumpReaderAndroid 45from blinkpy.web_tests.models import test_run_results 46from blinkpy.web_tests.port import base 47from blinkpy.web_tests.port import linux 48from blinkpy.web_tests.port import driver 49from blinkpy.web_tests.port import factory 50from blinkpy.web_tests.port import server_process 51 52 53# These are stub globals used for android-specific modules. We 54# don't import them unless we actually need a real Android port object, 55# in order to not have the dependency on all of the android and catapult 56# modules in non-Android ports. 57# pylint: disable=invalid-name 58battery_utils = None 59device_errors = None 60device_utils = None 61devil_chromium = None 62devil_env = None 63intent = None 64perf_control = None 65# pylint: enable=invalid-name 66 67_friendly_browser_names = { 68 'weblayershell': 'weblayer', 'systemwebviewshell': 'webview', 'chromepublic': 'chromium'} 69 70def _import_android_packages_if_necessary(): 71 # pylint: disable=invalid-name 72 global battery_utils 73 global device_errors 74 global device_utils 75 global devil_chromium 76 global devil_env 77 global intent 78 global perf_control 79 # pylint: enable=invalid-name 80 81 if not battery_utils: 82 chromium_src_root = get_chromium_src_dir() 83 devil_root = os.path.join(chromium_src_root, 'third_party', 'catapult', 84 'devil') 85 build_android_root = os.path.join(chromium_src_root, 'build', 'android') 86 sys.path.insert(0, devil_root) 87 sys.path.insert(0, build_android_root) 88 from importlib import import_module 89 90 battery_utils = import_module('devil.android.battery_utils') 91 devil_env = import_module('devil.devil_env') 92 device_errors = import_module('devil.android.device_errors') 93 device_utils = import_module('devil.android.device_utils') 94 devil_chromium = import_module('devil_chromium') 95 intent = import_module('devil.android.sdk.intent') 96 perf_control = import_module('devil.android.perf.perf_control') 97 98 99_log = logging.getLogger(__name__) 100 101# The root directory for test resources, which has the same structure as the 102# source root directory of Chromium. 103# This path is defined in Chromium's base/test/test_support_android.cc. 104DEVICE_SOURCE_ROOT_DIR = '/data/local/tmp/' 105 106# The web tests directory on device, which has two usages: 107# 1. as a virtual path in file urls that will be bridged to HTTP. 108# 2. pointing to some files that are pushed to the device for tests that 109# don't work on file-over-http (e.g. blob protocol tests). 110DEVICE_WEB_TESTS_DIR = DEVICE_SOURCE_ROOT_DIR + RELATIVE_WEB_TESTS 111 112KPTR_RESTRICT_PATH = '/proc/sys/kernel/kptr_restrict' 113 114# All the test cases are still served to the test runner through file protocol, 115# but we use a file-to-http feature to bridge the file request to host's http 116# server to get the real test files and corresponding resources. 117# See webkit/support/platform_support_android.cc for the other side of this bridge. 118# WEB_TEST_PATH_PREFIX should be matched to the local directory name of 119# web_tests because some tests and test_runner find test root directory 120# with it. 121PERF_TEST_PATH_PREFIX = '/PerformanceTests' 122WEB_TESTS_PATH_PREFIX = '/' + WEB_TESTS_LAST_COMPONENT 123 124# We start netcat processes for each of the three stdio streams. In doing so, 125# we attempt to use ports starting from 10201. This starting value is 126# completely arbitrary. 127FIRST_NETCAT_PORT = 10201 128 129# Timeout in seconds to wait for starting/stopping the driver. 130DRIVER_START_STOP_TIMEOUT_SECS = 10 131 132# Test resources that need to be accessed as files directly. 133# Each item can be the relative path of a directory or a file. 134TEST_RESOURCES_TO_PUSH = [ 135 # Blob tests need to access files directly. 136 'editing/pasteboard/resources', 137 'fast/files/resources', 138 'http/tests/local/resources', 139 'http/tests/local/formdata/resources', 140 # User style URLs are accessed as local files in webkit_support. 141 'http/tests/security/resources/cssStyle.css', 142 # Media tests need to access audio/video as files. 143 'media/content', 144 'compositing/resources/video.mp4', 145] 146 147 148class DriverDetails(object): 149 150 def __init__(self, apk): 151 self._apk = apk 152 153 def apk_name(self): 154 return self._apk 155 156 157# Information required when running web tests using content_shell as the test runner. 158class ContentShellDriverDetails(DriverDetails): 159 160 def __init__(self): 161 super(ContentShellDriverDetails, self).__init__('apks/ContentShell.apk') 162 163 def device_cache_directory(self): 164 return self.device_directory() + 'cache/' 165 166 def device_fonts_directory(self): 167 return self.device_directory() + 'fonts/' 168 169 def device_fifo_directory(self): 170 return '/data/data/' + self.package_name() + '/files/' 171 172 def package_name(self): 173 return 'org.chromium.content_shell_apk' 174 175 def activity_name(self): 176 return self.package_name() + '/.ContentShellActivity' 177 178 def library_name(self): 179 return 'libcontent_shell_content_view.so' 180 181 def command_line_file(self): 182 return '/data/local/tmp/content-shell-command-line' 183 184 def device_crash_dumps_directory(self): 185 return '/data/local/tmp/content-shell-crash-dumps' 186 187 def additional_command_line_flags(self, use_breakpad): 188 flags = ['--encode-binary'] 189 if use_breakpad: 190 flags.extend(['--enable-crash-reporter', '--crash-dumps-dir=%s' % self.device_crash_dumps_directory()]) 191 return flags 192 193 def device_directory(self): 194 return DEVICE_SOURCE_ROOT_DIR + 'content_shell/' 195 196 197# A class to encapsulate device status and information, such as the DeviceUtils 198# instances and whether the device has been set up. 199class AndroidDevices(object): 200 # Percentage of battery a device needs to have in order for it to be considered 201 # to participate in running the web tests. 202 MINIMUM_BATTERY_PERCENTAGE = 30 203 204 def __init__(self, default_devices=None, debug_logging=False): 205 self._usable_devices = [] 206 self._default_devices = default_devices 207 self._prepared_devices = [] 208 self._debug_logging = debug_logging 209 210 def prepared_devices(self): 211 return self._prepared_devices 212 213 def usable_devices(self, executive): 214 if self._usable_devices: 215 return self._usable_devices 216 217 if self._default_devices: 218 self._usable_devices = [ 219 device_utils.DeviceUtils(d) 220 for d in self._default_devices] 221 return self._usable_devices 222 223 devices = device_utils.DeviceUtils.HealthyDevices() 224 self._usable_devices = [ 225 d for d in devices 226 if (battery_utils.BatteryUtils(d).GetBatteryInfo().get('level', 0) 227 >= AndroidDevices.MINIMUM_BATTERY_PERCENTAGE 228 and d.IsScreenOn())] 229 230 return self._usable_devices 231 232 def get_device(self, executive, device_index): 233 devices = self.usable_devices(executive) 234 if device_index >= len(devices): 235 raise AssertionError('Device index exceeds number of usable devices.') 236 237 return devices[device_index] 238 239 def is_device_prepared(self, device_serial): 240 return device_serial in self._prepared_devices 241 242 def set_device_prepared(self, device_serial): 243 self._prepared_devices.append(device_serial) 244 245 246class AndroidPort(base.Port): 247 port_name = 'android' 248 249 # Avoid initializing the adb path [worker count]+1 times by storing it as a static member. 250 _adb_path = None 251 252 SUPPORTED_VERSIONS = ('android') 253 254 FALLBACK_PATHS = {'kitkat': ['android'] + linux.LinuxPort.latest_platform_fallback_path()} 255 256 BUILD_REQUIREMENTS_URL = 'https://www.chromium.org/developers/how-tos/android-build-instructions' 257 258 def __init__(self, host, port_name='', apk='', options=None, **kwargs): 259 super(AndroidPort, self).__init__(host, port_name, options=options, **kwargs) 260 self._operating_system = 'android' 261 self._version = 'kitkat' 262 fs = host.filesystem 263 self._local_port = factory.PortFactory(host).get(**kwargs) 264 265 if apk: 266 self._driver_details = DriverDetails(apk) 267 browser_type = fs.splitext(fs.basename(apk))[0].lower() 268 self._browser_type = _friendly_browser_names.get(browser_type, browser_type) 269 else: 270 # The legacy test runner will be used to run web tests on Android. 271 # So we need to initialize several port member variables. 272 _import_android_packages_if_necessary() 273 self._driver_details = ContentShellDriverDetails() 274 self._browser_type = 'content_shell' 275 self._debug_logging = self.get_option('android_logging') 276 self.server_process_constructor = self._android_server_process_constructor 277 278 if not self.get_option('disable_breakpad'): 279 self._dump_reader = DumpReaderAndroid(host, self._build_path()) 280 281 # Initialize the AndroidDevices class which tracks available devices. 282 default_devices = None 283 if hasattr(self._options, 'adb_devices') and len(self._options.adb_devices): 284 default_devices = self._options.adb_devices 285 286 self._devices = AndroidDevices(default_devices, self._debug_logging) 287 288 devil_chromium.Initialize( 289 output_directory=self._build_path(), 290 adb_path=self._path_from_chromium_base( 291 'third_party', 'android_sdk', 'public', 'platform-tools', 'adb')) 292 293 devil_env.config.InitializeLogging( 294 logging.DEBUG 295 if self._debug_logging and self.get_option('debug_rwt_logging') 296 else logging.WARNING) 297 298 prepared_devices = self.get_option('prepared_devices', []) 299 for serial in prepared_devices: 300 self._devices.set_device_prepared(serial) 301 302 def get_platform_tags(self): 303 _sanitize_tag = lambda t: t.replace('_', '-').replace(' ', '-') 304 return frozenset(['android', 'android-' + _sanitize_tag(self._browser_type)]) 305 306 def default_smoke_test_only(self): 307 return True 308 309 def additional_driver_flags(self): 310 return super(AndroidPort, self).additional_driver_flags() + \ 311 self._driver_details.additional_command_line_flags(use_breakpad=not self.get_option('disable_breakpad')) 312 313 def default_timeout_ms(self): 314 # Android platform has less computing power than desktop platforms. 315 # Using 10 seconds allows us to pass most slow tests which are not 316 # marked as slow tests on desktop platforms. 317 return 10 * 1000 318 319 def driver_stop_timeout(self): 320 # The driver doesn't respond to closing stdin, so we might as well stop the driver immediately. 321 return 0.0 322 323 def default_child_processes(self): 324 usable_devices = self._devices.usable_devices(self._executive) 325 if not usable_devices: 326 raise test_run_results.TestRunException(exit_codes.NO_DEVICES_EXIT_STATUS, 327 'Unable to find any attached Android devices.') 328 return len(usable_devices) 329 330 def check_build(self, needs_http, printer): 331 exit_status = super(AndroidPort, self).check_build(needs_http, printer) 332 if exit_status: 333 return exit_status 334 335 return self._check_devices(printer) 336 337 def _check_devices(self, printer): 338 339 # Push the executables and other files to the devices; doing this now 340 # means we can do this in parallel in the manager process and not mix 341 # this in with starting and stopping workers. 342 343 lock = threading.Lock() 344 345 def log_safely(msg, throttled=True): 346 if throttled: 347 callback = printer.write_throttled_update 348 else: 349 callback = printer.write_update 350 with lock: 351 callback('%s' % msg) 352 353 def _setup_device_impl(device): 354 log_safely('preparing device', throttled=False) 355 356 if self._devices.is_device_prepared(device.serial): 357 return 358 359 device.EnableRoot() 360 perf_control.PerfControl(device).SetPerfProfilingMode() 361 362 # Required by webkit_support::GetWebKitRootDirFilePath(). 363 # Other directories will be created automatically by adb push. 364 device.RunShellCommand( 365 ['mkdir', '-p', DEVICE_SOURCE_ROOT_DIR + 'chrome'], 366 check_return=True) 367 368 # Allow the test driver to get full read and write access to the directory on the device, 369 # as well as for the FIFOs. We'll need a world writable directory. 370 device.RunShellCommand( 371 ['mkdir', '-p', self._driver_details.device_directory()], 372 check_return=True) 373 374 # Make sure that the disk cache on the device resets to a clean state. 375 device.RunShellCommand( 376 ['rm', '-rf', self._driver_details.device_cache_directory()], 377 check_return=True) 378 379 device.EnableRoot() 380 perf_control.PerfControl(device).SetPerfProfilingMode() 381 382 # Required by webkit_support::GetWebKitRootDirFilePath(). 383 # Other directories will be created automatically by adb push. 384 device.RunShellCommand( 385 ['mkdir', '-p', DEVICE_SOURCE_ROOT_DIR + 'chrome'], 386 check_return=True) 387 388 # Allow the test driver to get full read and write access to the directory on the device, 389 # as well as for the FIFOs. We'll need a world writable directory. 390 device.RunShellCommand( 391 ['mkdir', '-p', self._driver_details.device_directory()], 392 check_return=True) 393 394 # Make sure that the disk cache on the device resets to a clean state. 395 device.RunShellCommand( 396 ['rm', '-rf', self._driver_details.device_cache_directory()], 397 check_return=True) 398 399 device_path = lambda *p: posixpath.join( 400 self._driver_details.device_directory(), *p) 401 402 device.Install(self._path_to_driver()) 403 404 # Build up a list of what we want to push, including: 405 host_device_tuples = [] 406 407 # - the custom font files 408 # TODO(sergeyu): Rename these files, they can be used on platforms 409 # other than Android. 410 host_device_tuples.append( 411 (self._build_path('test_fonts/android_main_fonts.xml'), 412 device_path('android_main_fonts.xml'))) 413 host_device_tuples.append( 414 (self._build_path('test_fonts/android_fallback_fonts.xml'), 415 device_path('android_fallback_fonts.xml'))) 416 for font_file in self._get_font_files(): 417 host_device_tuples.append( 418 (font_file, device_path('fonts', os.path.basename(font_file)))) 419 420 # - the test resources 421 host_device_tuples.extend( 422 (self.host.filesystem.join(self.web_tests_dir(), resource), 423 posixpath.join(DEVICE_WEB_TESTS_DIR, resource)) 424 for resource in TEST_RESOURCES_TO_PUSH) 425 426 # ... and then push them to the device. 427 device.PushChangedFiles(host_device_tuples) 428 429 device.RunShellCommand( 430 ['mkdir', '-p', self._driver_details.device_fifo_directory()], 431 check_return=True) 432 433 device.RunShellCommand( 434 ['chmod', '-R', '777', self._driver_details.device_directory()], 435 check_return=True) 436 device.RunShellCommand( 437 ['chmod', '-R', '777', self._driver_details.device_fifo_directory()], 438 check_return=True) 439 440 # Mark this device as having been set up. 441 self._devices.set_device_prepared(device.serial) 442 443 log_safely('device prepared', throttled=False) 444 445 def setup_device(device): 446 try: 447 _setup_device_impl(device) 448 except (ScriptError, 449 driver.DeviceFailure, 450 device_errors.CommandFailedError, 451 device_errors.CommandTimeoutError, 452 device_errors.DeviceUnreachableError) as error: 453 with lock: 454 _log.warning('[%s] failed to prepare_device: %s', device.serial, error) 455 456 devices = self._devices.usable_devices(self.host.executive) 457 device_utils.DeviceUtils.parallel(devices).pMap(setup_device) 458 459 if not self._devices.prepared_devices(): 460 _log.error('Could not prepare any devices for testing.') 461 return exit_codes.NO_DEVICES_EXIT_STATUS 462 return exit_codes.OK_EXIT_STATUS 463 464 def setup_test_run(self): 465 super(AndroidPort, self).setup_test_run() 466 467 # By setting this on the options object, we can propagate the list 468 # of prepared devices to the workers (it is read in __init__()). 469 if self._devices._prepared_devices: 470 self._options.prepared_devices = self._devices.prepared_devices() 471 else: 472 # We were called with --no-build, so assume the devices are up to date. 473 self._options.prepared_devices = [d.get_serial() for d in self._devices.usable_devices(self.host.executive)] 474 475 def num_workers(self, requested_num_workers): 476 return min(len(self._options.prepared_devices), requested_num_workers) 477 478 def check_sys_deps(self): 479 # _get_font_files() will throw if any of the required fonts is missing. 480 self._get_font_files() 481 return exit_codes.OK_EXIT_STATUS 482 483 def requires_http_server(self): 484 """Chromium Android runs tests on devices, and uses the HTTP server to 485 serve the actual web tests to the test driver. 486 """ 487 return True 488 489 def start_http_server(self, additional_dirs, number_of_drivers): 490 additional_dirs[PERF_TEST_PATH_PREFIX] = self._perf_tests_dir() 491 additional_dirs[WEB_TESTS_PATH_PREFIX] = self.web_tests_dir() 492 super(AndroidPort, self).start_http_server(additional_dirs, number_of_drivers) 493 494 def create_driver(self, worker_number, no_timeout=False): 495 return ChromiumAndroidDriver(self, worker_number, 496 driver_details=self._driver_details, 497 android_devices=self._devices, 498 # Force no timeout to avoid test driver timeouts before NRWT. 499 no_timeout=True) 500 501 def driver_cmd_line(self): 502 # Override to return the actual test driver's command line. 503 return self.create_driver(0)._android_driver_cmd_line([]) 504 505 def clobber_old_port_specific_results(self): 506 if not self.get_option('disable_breakpad'): 507 self._dump_reader.clobber_old_results() 508 509 # Overridden protected methods. 510 511 def _build_path(self, *comps): 512 return self._local_port._build_path(*comps) 513 514 def _build_path_with_target(self, target, *comps): 515 return self._local_port._build_path_with_target(target, *comps) 516 517 def path_to_apache(self): 518 return self._local_port.path_to_apache() 519 520 def path_to_apache_config_file(self): 521 return self._local_port.path_to_apache_config_file() 522 523 def _path_to_driver(self, target=None): 524 return self._build_path_with_target(target, self._driver_details.apk_name()) 525 526 def _path_to_image_diff(self): 527 return self._local_port._path_to_image_diff() 528 529 def _shut_down_http_server(self, pid): 530 return self._local_port._shut_down_http_server(pid) 531 532 def _driver_class(self): 533 return ChromiumAndroidDriver 534 535 # Local private methods. 536 537 @staticmethod 538 def _android_server_process_constructor(port, server_name, cmd_line, env=None, more_logging=False): 539 return server_process.ServerProcess(port, server_name, cmd_line, env, 540 treat_no_data_as_crash=True, more_logging=more_logging) 541 542 543class AndroidPerf(SingleFileOutputProfiler): 544 _cached_perf_host_path = None 545 _have_searched_for_perf_host = False 546 547 def __init__(self, host, executable_path, output_dir, device, symfs_path, kallsyms_path, identifier=None): 548 super(AndroidPerf, self).__init__(host, executable_path, output_dir, 'data', identifier) 549 self._device = device 550 self._perf_process = None 551 self._symfs_path = symfs_path 552 self._kallsyms_path = kallsyms_path 553 554 def check_configuration(self): 555 # Check that perf is installed 556 if not self._device.PathExists('/system/bin/perf'): 557 _log.error('Cannot find /system/bin/perf on device %s', self._device.serial) 558 return False 559 560 # Check that the device is a userdebug build (or at least has the necessary libraries). 561 if self._device.build_type != 'userdebug': 562 _log.error('Device %s is not flashed with a userdebug build of Android', self._device.serial) 563 return False 564 565 # FIXME: Check that the binary actually is perf-able (has stackframe pointers)? 566 # objdump -s a function and make sure it modifies the fp? 567 # Instruct users to rebuild after export GYP_DEFINES="profiling=1 $GYP_DEFINES" 568 return True 569 570 def print_setup_instructions(self): 571 _log.error(""" 572perf on android requires a 'userdebug' build of Android, see: 573http://source.android.com/source/building-devices.html" 574 575The perf command can be built from: 576https://android.googlesource.com/platform/external/linux-tools-perf/ 577and requires libefl, libebl, libdw, and libdwfl available in: 578https://android.googlesource.com/platform/external/elfutils/ 579 580The test driver must be built with profiling=1, make sure you've done: 581export GYP_DEFINES="profiling=1 $GYP_DEFINES" 582update-webkit --chromium-android 583build-webkit --chromium-android 584 585Googlers should read: 586http://goto.google.com/cr-android-perf-howto 587""") 588 589 def attach_to_pid(self, pid): 590 assert pid 591 assert self._perf_process is None 592 # FIXME: This can't be a fixed timeout! 593 cmd = [self._device.adb.GetAdbPath(), '-s', self._device.serial, 594 'shell', 'perf', 'record', '-g', '-p', pid, 'sleep', 30] 595 self._perf_process = self._host.executive.popen(cmd) 596 597 def _perf_version_string(self, perf_path): 598 try: 599 return self._host.executive.run_command([perf_path, '--version']) 600 except: 601 return None 602 603 def _find_perfhost_binary(self): 604 perfhost_version = self._perf_version_string('perfhost_linux') 605 if perfhost_version: 606 return 'perfhost_linux' 607 perf_version = self._perf_version_string('perf') 608 if perf_version: 609 return 'perf' 610 return None 611 612 def _perfhost_path(self): 613 if self._have_searched_for_perf_host: 614 return self._cached_perf_host_path 615 self._have_searched_for_perf_host = True 616 self._cached_perf_host_path = self._find_perfhost_binary() 617 return self._cached_perf_host_path 618 619 def _first_ten_lines_of_profile(self, perf_output): 620 match = re.search(r"^#[^\n]*\n((?: [^\n]*\n){1,10})", perf_output, re.MULTILINE) 621 return match.group(1) if match else None 622 623 def profile_after_exit(self): 624 perf_exitcode = self._perf_process.wait() 625 if perf_exitcode != 0: 626 _log.debug("Perf failed (exit code: %i), can't process results.", perf_exitcode) 627 return 628 629 self._device.PullFile('/data/perf.data', self._output_path) 630 631 perfhost_path = self._perfhost_path() 632 perfhost_report_command = [ 633 'report', 634 '--input', self._output_path, 635 '--symfs', self._symfs_path, 636 '--kallsyms', self._kallsyms_path, 637 ] 638 if perfhost_path: 639 perfhost_args = [perfhost_path] + perfhost_report_command + ['--call-graph', 'none'] 640 perf_output = self._host.executive.run_command(perfhost_args) 641 # We could save off the full -g report to a file if users found that useful. 642 _log.debug(self._first_ten_lines_of_profile(perf_output)) 643 else: 644 _log.debug(""" 645Failed to find perfhost_linux binary, can't process samples from the device. 646 647perfhost_linux can be built from: 648https://android.googlesource.com/platform/external/linux-tools-perf/ 649also, modern versions of perf (available from apt-get install goobuntu-kernel-tools-common) 650may also be able to process the perf.data files from the device. 651 652Googlers should read: 653http://goto.google.com/cr-android-perf-howto 654for instructions on installing pre-built copies of perfhost_linux 655http://crbug.com/165250 discusses making these pre-built binaries externally available. 656""") 657 658 perfhost_display_patch = perfhost_path if perfhost_path else 'perfhost_linux' 659 _log.debug('To view the full profile, run:') 660 _log.debug(' '.join([perfhost_display_patch] + perfhost_report_command)) 661 662 663class ChromiumAndroidDriver(driver.Driver): 664 665 def __init__(self, port, worker_number, driver_details, android_devices, no_timeout=False): 666 super(ChromiumAndroidDriver, self).__init__(port, worker_number, no_timeout) 667 self._write_stdin_process = None 668 self._read_stdout_process = None 669 self._read_stderr_process = None 670 self._original_kptr_restrict = None 671 672 self._android_devices = android_devices 673 self._device = android_devices.get_device(port._executive, worker_number) # pylint: disable=protected-access 674 self._driver_details = driver_details 675 self._debug_logging = self._port._debug_logging 676 self._created_cmd_line = False 677 self._device_failed = False 678 679 # FIXME: If we taught ProfileFactory about "target" devices we could 680 # just use the logic in Driver instead of duplicating it here. 681 if self._port.get_option('profile'): 682 # FIXME: This should be done once, instead of per-driver! 683 symfs_path = self._find_or_create_symfs() 684 kallsyms_path = self._update_kallsyms_cache(symfs_path) 685 # FIXME: We should pass this some sort of "Bridge" object abstraction around ADB instead of a path/device pair. 686 self._profiler = AndroidPerf(self._port.host, self._port._path_to_driver(), self._port.artifacts_directory(), 687 self._device, symfs_path, kallsyms_path) 688 # FIXME: This is a layering violation and should be moved to Port.check_sys_deps 689 # once we have an abstraction around an adb_path/device_serial pair to make it 690 # easy to make these class methods on AndroidPerf. 691 if not self._profiler.check_configuration(): 692 self._profiler.print_setup_instructions() 693 sys.exit(1) 694 else: 695 self._profiler = None 696 697 def __del__(self): 698 self._teardown_performance() 699 self._clean_up_cmd_line() 700 super(ChromiumAndroidDriver, self).__del__() 701 702 def _update_kallsyms_cache(self, output_dir): 703 kallsyms_name = '%s-kallsyms' % self._device.serial 704 kallsyms_cache_path = self._port.host.filesystem.join(output_dir, kallsyms_name) 705 706 self._device.EnableRoot() 707 708 saved_kptr_restrict = self._device.ReadFile(KPTR_RESTRICT_PATH).strip() 709 self._device.WriteFile(KPTR_RESTRICT_PATH, '0') 710 711 _log.debug('Updating kallsyms file (%s) from device', kallsyms_cache_path) 712 self._device.PullFile('/proc/kallsysm', kallsyms_cache_path) 713 self._device.WriteFile(KPTR_RESTRICT_PATH, saved_kptr_restrict) 714 715 return kallsyms_cache_path 716 717 def _find_or_create_symfs(self): 718 env = self._port.host.environ.copy() 719 fs = self._port.host.filesystem 720 721 if 'ANDROID_SYMFS' in env: 722 symfs_path = env['ANDROID_SYMFS'] 723 else: 724 symfs_path = fs.join(self._port.artifacts_directory(), 'symfs') 725 _log.debug('ANDROID_SYMFS not set, using %s', symfs_path) 726 727 # find the installed path, and the path of the symboled built library 728 # FIXME: We should get the install path from the device! 729 symfs_library_path = fs.join(symfs_path, 'data/app-lib/%s-1/%s' % 730 (self._driver_details.package_name(), self._driver_details.library_name())) 731 built_library_path = self._port._build_path('lib', self._driver_details.library_name()) 732 assert fs.exists(built_library_path) 733 734 # FIXME: Ideally we'd check the sha1's first and make a soft-link instead 735 # of copying (since we probably never care about windows). 736 _log.debug('Updating symfs library (%s) from built copy (%s)', symfs_library_path, built_library_path) 737 fs.maybe_make_directory(fs.dirname(symfs_library_path)) 738 fs.copyfile(built_library_path, symfs_library_path) 739 740 return symfs_path 741 742 def _log_error(self, message): 743 _log.error('[%s] %s', self._device.serial, message) 744 745 def _log_warning(self, message): 746 _log.warning('[%s] %s', self._device.serial, message) 747 748 def _log_debug(self, message): 749 if self._debug_logging: 750 _log.debug('[%s] %s', self._device.serial, message) 751 752 def _abort(self, message): 753 self._device_failed = True 754 raise driver.DeviceFailure('[%s] %s' % (self._device.serial, message)) 755 756 def _get_last_stacktrace(self): 757 try: 758 tombstones = self._device.RunShellCommand( 759 'ls -n /data/tombstones/tombstone_*', 760 check_return=True, shell=True) 761 except device_errors.CommandFailedError as exc: 762 # FIXME: crbug.com/321489 ... figure out why we sometimes get 763 # permission denied. 764 self._log_error('The driver crashed, but we were unable to read a tombstone: %s' % str(exc)) 765 return '' 766 767 last_tombstone = None 768 for tombstone in tombstones: 769 # Format of fields: 770 # 0 1 2 3 4 5 6 771 # permission uid gid size date time filename 772 # -rw------- 1000 1000 45859 2011-04-13 06:00 tombstone_00 773 fields = tombstone.split() 774 if len(fields) != 7: 775 self._log_warning("unexpected line in tombstone output, skipping: '%s'" % tombstone) 776 continue 777 778 if not last_tombstone or fields[4] + fields[5] >= last_tombstone[4] + last_tombstone[5]: 779 last_tombstone = fields 780 else: 781 break 782 783 if not last_tombstone: 784 self._log_error('The driver crashed, but we could not find any valid tombstone!') 785 return '' 786 787 # Use Android tool vendor/google/tools/stack to convert the raw 788 # stack trace into a human readable format, if needed. 789 # It takes a long time, so don't do it here. 790 tombstone_contents = self._device.ReadFile( 791 '/data/tombstones/%s' % last_tombstone[6]) 792 return '%s\n%s' % (' '.join(last_tombstone), tombstone_contents) 793 794 def _get_logcat(self): 795 return '\n'.join(self._device.adb.Logcat(dump=True, logcat_format='threadtime')) 796 797 def _teardown_performance(self): 798 perf_control.PerfControl(self._device).SetDefaultPerfMode() 799 800 def _get_crash_log(self, stdout, stderr, newer_than): 801 if not stdout: 802 stdout = '' 803 stdout += '********* [%s] Logcat:\n%s' % (self._device.serial, self._get_logcat()) 804 if not stderr: 805 stderr = '' 806 stderr += '********* [%s] Tombstone file:\n%s' % (self._device.serial, self._get_last_stacktrace()) 807 808 if not self._port.get_option('disable_breakpad'): 809 crashes = self._pull_crash_dumps_from_device() 810 for crash in crashes: 811 stack = self._port._dump_reader._get_stack_from_dump(crash) # pylint: disable=protected-access 812 try: 813 stack_str = stack.encode('ascii', 'replace') 814 except Exception as e: 815 stack_str = '<No Stack> (%s)' % e 816 stderr += '********* [%s] breakpad minidump %s:\n%s' % ( 817 self._port.host.filesystem.basename(crash), 818 self._device.serial, 819 stack_str) 820 821 return super(ChromiumAndroidDriver, self)._get_crash_log( 822 stdout, stderr, newer_than) 823 824 def cmd_line(self, per_test_args): 825 # The returned command line is used to start _server_process. In our case, it's an interactive 'adb shell'. 826 # The command line passed to the driver process is returned by _driver_cmd_line() instead. 827 return [self._device.adb.GetAdbPath(), '-s', self._device.serial, 'shell'] 828 829 def _android_driver_cmd_line(self, per_test_args): 830 return driver.Driver.cmd_line(self, per_test_args) 831 832 @staticmethod 833 def _loop_with_timeout(condition, timeout_secs): 834 deadline = time.time() + timeout_secs 835 while time.time() < deadline: 836 if condition(): 837 return True 838 return False 839 840 def start(self, per_test_args, deadline): 841 # We override the default start() so that we can call _android_driver_cmd_line() 842 # instead of cmd_line(). 843 new_cmd_line = self._android_driver_cmd_line(per_test_args) 844 845 # Since _android_driver_cmd_line() is different than cmd_line() we need to provide 846 # our own mechanism for detecting when the process should be stopped. 847 if self._current_cmd_line is None: 848 self._current_android_cmd_line = None 849 if new_cmd_line != self._current_android_cmd_line: 850 self.stop() 851 self._current_android_cmd_line = new_cmd_line 852 853 super(ChromiumAndroidDriver, self).start(per_test_args, deadline) 854 855 def _start(self, per_test_args): 856 if not self._android_devices.is_device_prepared(self._device.serial): 857 raise driver.DeviceFailure('%s is not prepared in _start()' % self._device.serial) 858 859 for retries in range(3): 860 try: 861 if self._start_once(per_test_args): 862 return 863 except ScriptError as error: 864 self._abort('ScriptError("%s") in _start()' % error) 865 866 self._log_error('Failed to start the content_shell application. Retries=%d. Log:\n%s' % (retries, self._get_logcat())) 867 self.stop() 868 time.sleep(2) 869 self._abort('Failed to start the content_shell application multiple times. Giving up.') 870 871 def _start_once(self, per_test_args): 872 super(ChromiumAndroidDriver, self)._start(per_test_args, wait_for_ready=False) 873 874 self._device.adb.Logcat(clear=True) 875 876 self._create_device_crash_dumps_directory() 877 878 # Read back the shell prompt to ensure adb shell is ready. 879 deadline = time.time() + DRIVER_START_STOP_TIMEOUT_SECS 880 self._server_process.start() 881 self._read_prompt(deadline) 882 self._log_debug('Interactive shell started') 883 884 # Start a netcat process to which the test driver will connect to write stdout. 885 self._read_stdout_process, stdout_port = self._start_netcat( 886 'ReadStdout', read_from_stdin=False) 887 self._log_debug('Redirecting stdout to port %d' % stdout_port) 888 889 # Start a netcat process to which the test driver will connect to write stderr. 890 self._read_stderr_process, stderr_port = self._start_netcat( 891 'ReadStderr', first_port=stdout_port + 1, read_from_stdin=False) 892 self._log_debug('Redirecting stderr to port %d' % stderr_port) 893 894 # Start a netcat process to which the test driver will connect to read stdin. 895 self._write_stdin_process, stdin_port = self._start_netcat( 896 'WriteStdin', first_port=stderr_port + 1) 897 self._log_debug('Redirecting stdin to port %d' % stdin_port) 898 899 # Combine the stdin, stdout, and stderr pipes into self._server_process. 900 self._replace_server_process_streams() 901 902 # We delay importing forwarder as long as possible because it uses fcntl, 903 # which isn't available on windows. 904 from devil.android import forwarder 905 906 self._log_debug('Starting forwarder') 907 forwarder.Forwarder.Map( 908 [(p, p) for p in base.Port.SERVER_PORTS], 909 self._device) 910 forwarder.Forwarder.Map( 911 [(forwarder.DYNAMIC_DEVICE_PORT, p) 912 for p in (stdout_port, stderr_port, stdin_port)], 913 self._device) 914 915 cmd_line_file_path = self._driver_details.command_line_file() 916 original_cmd_line_file_path = cmd_line_file_path + '.orig' 917 if (self._device.PathExists(cmd_line_file_path) 918 and not self._device.PathExists(original_cmd_line_file_path)): 919 # We check for both the normal path and the backup because we do not want to step 920 # on the backup. Otherwise, we'd clobber the backup whenever we changed the 921 # command line during the run. 922 self._device.RunShellCommand( 923 ['mv', cmd_line_file_path, original_cmd_line_file_path], 924 check_return=True) 925 926 stream_port_args = [ 927 '--android-stderr-port=%s' % forwarder.Forwarder.DevicePortForHostPort(stderr_port), 928 '--android-stdin-port=%s' % forwarder.Forwarder.DevicePortForHostPort(stdin_port), 929 '--android-stdout-port=%s' % forwarder.Forwarder.DevicePortForHostPort(stdout_port), 930 ] 931 cmd_line_contents = self._android_driver_cmd_line(per_test_args + stream_port_args) 932 self._device.WriteFile( 933 self._driver_details.command_line_file(), 934 ' '.join(cmd_line_contents)) 935 self._log_debug('Command-line file contents: %s' % ' '.join(cmd_line_contents)) 936 self._created_cmd_line = True 937 938 try: 939 self._device.StartActivity( 940 intent.Intent( 941 component=self._driver_details.activity_name(), 942 extras={'RunInSubThread': None})) 943 except device_errors.CommandFailedError as exc: 944 self._log_error('Failed to start the content_shell application. Exception:\n' + str(exc)) 945 return False 946 947 # The test driver might crash during startup. 948 if not self._wait_for_server_process_output(self._server_process, deadline, '#READY'): 949 return False 950 951 self._log_debug('content_shell is ready') 952 return True 953 954 def _create_device_crash_dumps_directory(self): 955 self._device.RunShellCommand( 956 ['rm', '-rf', self._driver_details.device_crash_dumps_directory()], 957 check_return=True) 958 self._device.RunShellCommand( 959 ['mkdir', self._driver_details.device_crash_dumps_directory()], 960 check_return=True) 961 self._device.RunShellCommand( 962 ['chmod', '-R', '777', self._driver_details.device_crash_dumps_directory()], 963 check_return=True) 964 965 def _start_netcat(self, server_name, first_port=FIRST_NETCAT_PORT, read_from_stdin=True): 966 for i in itertools.count(first_port, 65536): 967 nc_cmd = ['nc', '-l', str(i)] 968 if not read_from_stdin: 969 nc_cmd.append('-d') 970 proc = self._port.server_process_constructor(self._port, server_name, nc_cmd) 971 proc.start() 972 self._port.host.executive.wait_limited(proc.pid(), limit_in_seconds=1) 973 if self._port.host.executive.check_running_pid(proc.pid()): 974 return (proc, i) 975 976 raise Exception( 977 'Unable to find a port for netcat process %s' % server_name) 978 979 def _replace_server_process_streams(self): 980 # pylint: disable=protected-access 981 self._server_process.replace_input( 982 self._write_stdin_process._proc.stdin) 983 self._server_process.replace_outputs( 984 self._read_stdout_process._proc.stdout, 985 self._read_stderr_process._proc.stdout) 986 987 def _pid_on_target(self): 988 pids = self._device.GetPids(self._driver_details.package_name()) 989 return pids.get(self._driver_details.package_name()) 990 991 def stop(self): 992 if not self._device_failed: 993 # Do not try to stop the application if there's something wrong with the device; adb may hang. 994 # FIXME: crbug.com/305040. Figure out if it's really hanging (and why). 995 self._device.ForceStop(self._driver_details.package_name()) 996 997 if self._write_stdin_process: 998 self._write_stdin_process.kill() 999 self._write_stdin_process = None 1000 1001 if self._read_stdout_process: 1002 self._read_stdout_process.kill() 1003 self._read_stdout_process = None 1004 1005 if self._read_stderr_process: 1006 self._read_stderr_process.kill() 1007 self._read_stderr_process = None 1008 1009 # We delay importing forwarder as long as possible because it uses fcntl, 1010 # which isn't available on windows. 1011 from devil.android import forwarder 1012 1013 forwarder.Forwarder.KillDevice(self._device) 1014 forwarder.Forwarder.KillHost() 1015 1016 super(ChromiumAndroidDriver, self).stop() 1017 1018 self._clean_up_cmd_line() 1019 1020 def _pull_crash_dumps_from_device(self): 1021 result = [] 1022 if not self._device.PathExists(self._driver_details.device_crash_dumps_directory()): 1023 return result 1024 dumps = self._device.ListDirectory( 1025 self._driver_details.device_crash_dumps_directory()) 1026 for dump in dumps: 1027 device_dump = '%s/%s' % (self._driver_details.device_crash_dumps_directory(), dump) 1028 local_dump = self._port.host.filesystem.join( 1029 self._port._dump_reader.crash_dumps_directory(), dump) # pylint: disable=protected-access 1030 1031 # FIXME: crbug.com/321489. Figure out why these commands would fail ... 1032 try: 1033 self._device.RunShellCommand( 1034 ['chmod', '777', device_dump], check_return=True) 1035 self._device.PullFile(device_dump, local_dump) 1036 self._device.RunShellCommand( 1037 ['rm', '-f', device_dump], check_return=True) 1038 except device_errors.CommandFailedError: 1039 pass 1040 1041 if self._port.host.filesystem.exists(local_dump): 1042 result.append(local_dump) 1043 return result 1044 1045 def _clean_up_cmd_line(self): 1046 if not self._created_cmd_line: 1047 return 1048 1049 cmd_line_file_path = self._driver_details.command_line_file() 1050 original_cmd_line_file_path = cmd_line_file_path + '.orig' 1051 if self._device.PathExists(original_cmd_line_file_path): 1052 self._device.RunShellCommand( 1053 ['mv', original_cmd_line_file_path, cmd_line_file_path], 1054 check_return=True) 1055 elif self._device.PathExists(cmd_line_file_path): 1056 self._device.RunShellCommand( 1057 ['rm', cmd_line_file_path], 1058 check_return=True) 1059 self._created_cmd_line = False 1060 1061 def _command_from_driver_input(self, driver_input): 1062 command = super(ChromiumAndroidDriver, self)._command_from_driver_input(driver_input) 1063 if command.startswith('/'): 1064 command = 'http://127.0.0.1:8000' + WEB_TESTS_PATH_PREFIX + \ 1065 '/' + self._port.relative_test_filename(command) 1066 return command 1067 1068 def _read_prompt(self, deadline): 1069 last_char = '' 1070 while True: 1071 current_char = self._server_process.read_stdout(deadline, 1) 1072 if current_char == ' ': 1073 if last_char in ('#', '$'): 1074 return 1075 last_char = current_char 1076