1#!/usr/bin/env python 2 3# This Source Code Form is subject to the terms of the Mozilla Public 4# License, v. 2.0. If a copy of the MPL was not distributed with this 5# file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 7from __future__ import absolute_import 8 9import json 10import os 11import re 12import shutil 13import subprocess 14import sys 15import tempfile 16import traceback 17from abc import ABCMeta, abstractmethod 18 19import mozinfo 20import mozprocess 21import mozproxy.utils as mpu 22import mozversion 23import six 24 25from mozprofile import create_profile 26from mozproxy import get_playback 27 28# need this so raptor imports work both from /raptor and via mach 29here = os.path.abspath(os.path.dirname(__file__)) 30paths = [here] 31 32webext_dir = os.path.join(here, "..", "webext") 33paths.append(webext_dir) 34 35for path in paths: 36 if not os.path.exists(path): 37 raise IOError("%s does not exist. " % path) 38 sys.path.insert(0, path) 39 40from cmdline import FIREFOX_ANDROID_APPS 41from condprof.client import get_profile, ProfileNotFoundError 42from condprof.util import get_current_platform 43from logger.logger import RaptorLogger 44from gecko_profile import GeckoProfile 45from results import RaptorResultsHandler 46 47LOG = RaptorLogger(component="raptor-perftest") 48 49# - mozproxy.utils LOG displayed INFO messages even when LOG.error() was used in mitm.py 50mpu.LOG = RaptorLogger(component="raptor-mitmproxy") 51 52try: 53 from mozbuild.base import MozbuildObject 54 55 build = MozbuildObject.from_environment(cwd=here) 56except ImportError: 57 build = None 58 59POST_DELAY_CONDPROF = 1000 60POST_DELAY_DEBUG = 3000 61POST_DELAY_DEFAULT = 30000 62 63 64@six.add_metaclass(ABCMeta) 65class Perftest(object): 66 """Abstract base class for perftests that execute via a subharness, 67 either Raptor or browsertime.""" 68 69 def __init__( 70 self, 71 app, 72 binary, 73 run_local=False, 74 noinstall=False, 75 obj_path=None, 76 profile_class=None, 77 installerpath=None, 78 gecko_profile=False, 79 gecko_profile_interval=None, 80 gecko_profile_entries=None, 81 gecko_profile_extra_threads=None, 82 gecko_profile_threads=None, 83 gecko_profile_features=None, 84 symbols_path=None, 85 host=None, 86 power_test=False, 87 cpu_test=False, 88 cold=False, 89 memory_test=False, 90 live_sites=False, 91 is_release_build=False, 92 debug_mode=False, 93 post_startup_delay=POST_DELAY_DEFAULT, 94 interrupt_handler=None, 95 e10s=True, 96 enable_webrender=False, 97 results_handler_class=RaptorResultsHandler, 98 device_name=None, 99 disable_perf_tuning=False, 100 conditioned_profile=None, 101 chimera=False, 102 extra_prefs={}, 103 environment={}, 104 project="mozilla-central", 105 verbose=False, 106 **kwargs 107 ): 108 self._dirs_to_remove = [] 109 self.verbose = verbose 110 111 # Override the magic --host HOST_IP with the value of the environment variable. 112 if host == "HOST_IP": 113 host = os.environ["HOST_IP"] 114 115 self.config = { 116 "app": app, 117 "binary": binary, 118 "platform": mozinfo.os, 119 "processor": mozinfo.processor, 120 "run_local": run_local, 121 "obj_path": obj_path, 122 "gecko_profile": gecko_profile, 123 "gecko_profile_interval": gecko_profile_interval, 124 "gecko_profile_entries": gecko_profile_entries, 125 "gecko_profile_extra_threads": gecko_profile_extra_threads, 126 "gecko_profile_threads": gecko_profile_threads, 127 "gecko_profile_features": gecko_profile_features, 128 "symbols_path": symbols_path, 129 "host": host, 130 "power_test": power_test, 131 "memory_test": memory_test, 132 "cpu_test": cpu_test, 133 "cold": cold, 134 "live_sites": live_sites, 135 "is_release_build": is_release_build, 136 "enable_control_server_wait": memory_test or cpu_test, 137 "e10s": e10s, 138 "enable_webrender": enable_webrender, 139 "device_name": device_name, 140 "enable_fission": extra_prefs.get("fission.autostart", False), 141 "disable_perf_tuning": disable_perf_tuning, 142 "conditioned_profile": conditioned_profile, 143 "chimera": chimera, 144 "extra_prefs": extra_prefs, 145 "environment": environment, 146 "project": project, 147 "verbose": verbose, 148 } 149 150 self.firefox_android_apps = FIREFOX_ANDROID_APPS 151 152 # We are deactivating the conditioned profiles for: 153 # - win10-aarch64 : no support for geckodriver see 1582757 154 # - reference browser: no conditioned profiles created see 1606767 155 if ( 156 self.config["platform"] == "win" and self.config["processor"] == "aarch64" 157 ) or self.config["binary"] == "org.mozilla.reference.browser.raptor": 158 self.config["conditioned_profile"] = None 159 160 if self.config["conditioned_profile"]: 161 LOG.info("Using a conditioned profile.") 162 else: 163 LOG.info("Using an empty profile.") 164 165 # To differentiate between chrome/firefox failures, we 166 # set an app variable in the logger which prefixes messages 167 # with the app name 168 if self.config["app"] in ("chrome", "chrome-m", "chromium"): 169 LOG.set_app(self.config["app"]) 170 171 self.browser_name = None 172 self.browser_version = None 173 174 self.raptor_venv = os.path.join(os.getcwd(), "raptor-venv") 175 self.installerpath = installerpath 176 self.playback = None 177 self.benchmark = None 178 self.gecko_profiler = None 179 self.device = None 180 self.runtime_error = None 181 self.profile_class = profile_class or app 182 self.conditioned_profile_dir = None 183 self.interrupt_handler = interrupt_handler 184 self.results_handler = results_handler_class(**self.config) 185 186 self.browser_name, self.browser_version = self.get_browser_meta() 187 188 browser_name, browser_version = self.get_browser_meta() 189 self.results_handler.add_browser_meta(self.config["app"], browser_version) 190 191 # debug mode is currently only supported when running locally 192 self.run_local = self.config["run_local"] 193 self.debug_mode = debug_mode if self.run_local else False 194 195 # For the post startup delay, we want to max it to 1s when using the 196 # conditioned profiles. 197 if self.config.get("conditioned_profile"): 198 self.post_startup_delay = min(post_startup_delay, POST_DELAY_CONDPROF) 199 else: 200 # if running debug-mode reduce the pause after browser startup 201 if self.debug_mode: 202 self.post_startup_delay = min(post_startup_delay, POST_DELAY_DEBUG) 203 else: 204 self.post_startup_delay = post_startup_delay 205 206 if self.config["enable_webrender"]: 207 self.config["environment"]["MOZ_WEBRENDER"] = "1" 208 else: 209 self.config["environment"]["MOZ_WEBRENDER"] = "0" 210 211 LOG.info("Post startup delay set to %d ms" % self.post_startup_delay) 212 LOG.info("main raptor init, config is: %s" % str(self.config)) 213 self.build_browser_profile() 214 215 # Crashes counter 216 self.crashes = 0 217 218 def _get_temp_dir(self): 219 tempdir = tempfile.mkdtemp() 220 self._dirs_to_remove.append(tempdir) 221 return tempdir 222 223 @property 224 def is_localhost(self): 225 return self.config.get("host") in ("localhost", "127.0.0.1") 226 227 @property 228 def conditioned_profile_copy(self): 229 """Returns a copy of the original conditioned profile that was created.""" 230 condprof_copy = os.path.join(self._get_temp_dir(), "profile") 231 shutil.copytree( 232 self.conditioned_profile_dir, 233 condprof_copy, 234 ignore=shutil.ignore_patterns("lock"), 235 ) 236 LOG.info("Created a conditioned-profile copy: %s" % condprof_copy) 237 return condprof_copy 238 239 def build_conditioned_profile(self): 240 # Late import so python-test doesn't import it 241 import asyncio 242 from condprof.runner import Runner 243 244 # The following import patchs an issue with invalid 245 # content-type, see bug 1655869 246 from condprof import patch # noqa 247 248 if not getattr(self, "browsertime"): 249 raise Exception( 250 "Building conditioned profiles within a test is only supported " 251 "when using Browsertime." 252 ) 253 254 geckodriver = getattr(self, "browsertime_geckodriver", None) 255 if not geckodriver: 256 geckodriver = ( 257 sys.platform.startswith("win") and "geckodriver.exe" or "geckodriver" 258 ) 259 260 scenario = self.config.get("conditioned_profile") 261 runner = Runner( 262 profile=None, 263 firefox=self.config.get("binary"), 264 geckodriver=geckodriver, 265 archive=None, 266 device_name=self.config.get("device_name"), 267 strict=True, 268 visible=True, 269 force_new=True, 270 skip_logs=True, 271 ) 272 273 if self.config.get("is_release_build", False): 274 # Enable non-local connections for building the conditioned profile 275 self.enable_non_local_connections() 276 277 if scenario == "settled-youtube": 278 runner.prepare(scenario, "youtube") 279 280 loop = asyncio.get_event_loop() 281 loop.run_until_complete(runner.one_run(scenario, "youtube")) 282 else: 283 runner.prepare(scenario, "default") 284 285 loop = asyncio.get_event_loop() 286 loop.run_until_complete(runner.one_run(scenario, "default")) 287 288 if self.config.get("is_release_build", False): 289 self.disable_non_local_connections() 290 291 self.conditioned_profile_dir = runner.env.profile 292 return self.conditioned_profile_copy 293 294 def get_conditioned_profile(self, binary=None): 295 """Downloads a platform-specific conditioned profile, using the 296 condprofile client API; returns a self.conditioned_profile_dir""" 297 if self.conditioned_profile_dir: 298 # We already have a directory, so provide a copy that 299 # will get deleted after it's done with 300 return self.conditioned_profile_copy 301 302 # Build the conditioned profile before the test 303 if not self.config.get("conditioned_profile").startswith("artifact:"): 304 return self.build_conditioned_profile() 305 306 # create a temp file to help ensure uniqueness 307 temp_download_dir = self._get_temp_dir() 308 LOG.info( 309 "Making temp_download_dir from inside get_conditioned_profile {}".format( 310 temp_download_dir 311 ) 312 ) 313 # call condprof's client API to yield our platform-specific 314 # conditioned-profile binary 315 if isinstance(self, PerftestAndroid): 316 android_app = self.config["binary"].split("org.mozilla.")[-1] 317 device_name = self.config.get("device_name") 318 if device_name is None: 319 device_name = "g5" 320 platform = "%s-%s" % (device_name, android_app) 321 else: 322 platform = get_current_platform() 323 324 LOG.info("Platform used: %s" % platform) 325 326 # when running under mozharness, the --project value 327 # is set to match the project (try, mozilla-central, etc.) 328 # By default it's mozilla-central, even on local runs. 329 # We use it to prioritize conditioned profiles indexed 330 # into the same project when it runs on the CI 331 repo = self.config["project"] 332 333 # we fall back to mozilla-central in all cases. If it 334 # was already mozilla-central, we fall back to try 335 alternate_repo = "mozilla-central" if repo != "mozilla-central" else "try" 336 LOG.info("Getting profile from project %s" % repo) 337 338 profile_scenario = self.config.get("conditioned_profile").replace( 339 "artifact:", "" 340 ) 341 try: 342 cond_prof_target_dir = get_profile( 343 temp_download_dir, platform, profile_scenario, repo=repo 344 ) 345 except ProfileNotFoundError: 346 cond_prof_target_dir = get_profile( 347 temp_download_dir, platform, profile_scenario, repo=alternate_repo 348 ) 349 except Exception: 350 # any other error is a showstopper 351 LOG.critical("Could not get the conditioned profile") 352 traceback.print_exc() 353 raise 354 355 # now get the full directory path to our fetched conditioned profile 356 self.conditioned_profile_dir = os.path.join( 357 temp_download_dir, cond_prof_target_dir 358 ) 359 if not os.path.exists(cond_prof_target_dir): 360 LOG.critical( 361 "Can't find target_dir {}, from get_profile()" 362 "temp_download_dir {}, platform {}, scenario {}".format( 363 cond_prof_target_dir, temp_download_dir, platform, profile_scenario 364 ) 365 ) 366 raise OSError 367 368 LOG.info( 369 "Original self.conditioned_profile_dir is now set: {}".format( 370 self.conditioned_profile_dir 371 ) 372 ) 373 return self.conditioned_profile_copy 374 375 def build_browser_profile(self): 376 if ( 377 self.config["app"] in ["chrome", "chromium", "chrome-m"] 378 or self.config.get("conditioned_profile") is None 379 ): 380 self.profile = create_profile(self.profile_class) 381 else: 382 # use mozprofile to create a profile for us, from our conditioned profile's path 383 self.profile = create_profile( 384 self.profile_class, profile=self.get_conditioned_profile() 385 ) 386 # Merge extra profile data from testing/profiles 387 with open(os.path.join(self.profile_data_dir, "profiles.json"), "r") as fh: 388 base_profiles = json.load(fh)["raptor"] 389 390 for profile in base_profiles: 391 path = os.path.join(self.profile_data_dir, profile) 392 LOG.info("Merging profile: {}".format(path)) 393 self.profile.merge(path) 394 395 if self.config["extra_prefs"].get("fission.autostart", False): 396 LOG.info("Enabling fission via browser preferences") 397 LOG.info("Browser preferences: {}".format(self.config["extra_prefs"])) 398 self.profile.set_preferences(self.config["extra_prefs"]) 399 400 # share the profile dir with the config and the control server 401 self.config["local_profile_dir"] = self.profile.profile 402 LOG.info("Local browser profile: {}".format(self.profile.profile)) 403 404 @property 405 def profile_data_dir(self): 406 if "MOZ_DEVELOPER_REPO_DIR" in os.environ: 407 return os.path.join( 408 os.environ["MOZ_DEVELOPER_REPO_DIR"], "testing", "profiles" 409 ) 410 if build: 411 return os.path.join(build.topsrcdir, "testing", "profiles") 412 return os.path.join(here, "profile_data") 413 414 @property 415 def artifact_dir(self): 416 artifact_dir = os.getcwd() 417 if self.config.get("run_local", False): 418 if "MOZ_DEVELOPER_REPO_DIR" in os.environ: 419 artifact_dir = os.path.join( 420 os.environ["MOZ_DEVELOPER_REPO_DIR"], 421 "testing", 422 "mozharness", 423 "build", 424 ) 425 else: 426 artifact_dir = here 427 elif os.getenv("MOZ_UPLOAD_DIR"): 428 artifact_dir = os.getenv("MOZ_UPLOAD_DIR") 429 return artifact_dir 430 431 @abstractmethod 432 def run_test_setup(self, test): 433 LOG.info("starting test: %s" % test["name"]) 434 435 # if 'alert_on' was provided in the test INI, add to our config for results/output 436 self.config["subtest_alert_on"] = test.get("alert_on") 437 438 if test.get("playback") is not None and self.playback is None: 439 self.start_playback(test) 440 441 if test.get("preferences") is not None: 442 self.set_browser_test_prefs(test["preferences"]) 443 444 @abstractmethod 445 def setup_chrome_args(self): 446 pass 447 448 @abstractmethod 449 def get_browser_meta(self): 450 pass 451 452 def run_tests(self, tests, test_names): 453 try: 454 for test in tests: 455 try: 456 self.run_test(test, timeout=int(test.get("page_timeout"))) 457 except RuntimeError as e: 458 # Check for crashes before showing the timeout error. 459 self.check_for_crashes() 460 if self.crashes == 0: 461 LOG.critical(e) 462 os.sys.exit(1) 463 finally: 464 self.run_test_teardown(test) 465 return self.process_results(tests, test_names) 466 finally: 467 self.clean_up() 468 469 @abstractmethod 470 def run_test(self, test, timeout): 471 raise NotImplementedError() 472 473 @abstractmethod 474 def run_test_teardown(self, test): 475 self.check_for_crashes() 476 477 def process_results(self, tests, test_names): 478 # when running locally output results in build/raptor.json; when running 479 # in production output to a local.json to be turned into tc job artifact 480 raptor_json_path = os.path.join(self.artifact_dir, "raptor.json") 481 if not self.config.get("run_local", False): 482 raptor_json_path = os.path.join(os.getcwd(), "local.json") 483 484 self.config["raptor_json_path"] = raptor_json_path 485 self.config["artifact_dir"] = self.artifact_dir 486 res = self.results_handler.summarize_and_output(self.config, tests, test_names) 487 488 # gecko profiling symbolication 489 if self.config["gecko_profile"]: 490 self.gecko_profiler.symbolicate() 491 # clean up the temp gecko profiling folders 492 LOG.info("cleaning up after gecko profiling") 493 self.gecko_profiler.clean() 494 495 return res 496 497 @abstractmethod 498 def set_browser_test_prefs(self): 499 pass 500 501 @abstractmethod 502 def check_for_crashes(self): 503 pass 504 505 def clean_up(self): 506 for dir_to_rm in self._dirs_to_remove: 507 if not os.path.exists(dir_to_rm): 508 continue 509 LOG.info("Removing temporary directory: {}".format(dir_to_rm)) 510 shutil.rmtree(dir_to_rm, ignore_errors=True) 511 self._dirs_to_remove = [] 512 513 def get_page_timeout_list(self): 514 return self.results_handler.page_timeout_list 515 516 def get_recording_paths(self, test): 517 recordings = test.get("playback_recordings") 518 519 if recordings: 520 recording_paths = [] 521 proxy_dir = self.playback.mozproxy_dir 522 523 for recording in recordings.split(): 524 if not recording: 525 continue 526 recording_paths.append(os.path.join(proxy_dir, recording)) 527 528 return recording_paths 529 530 def log_recording_dates(self, test): 531 _recording_paths = self.get_recording_paths(test) 532 if _recording_paths is None: 533 LOG.info( 534 "No playback recordings specified in the test; so not getting recording info" 535 ) 536 return 537 538 for r in _recording_paths: 539 json_path = "{}.json".format(r.split(".")[0]) 540 541 if os.path.exists(json_path): 542 with open(json_path) as f: 543 recording_date = json.loads(f.read()).get("recording_date") 544 545 if recording_date is not None: 546 LOG.info( 547 "Playback recording date: {} ".format( 548 recording_date.split(" ")[0] 549 ) 550 ) 551 else: 552 LOG.info("Playback recording date not available") 553 else: 554 LOG.info("Playback recording information not available") 555 556 def delete_proxy_settings_from_profile(self): 557 # Must delete the proxy settings from the profile if running 558 # the test with a host different from localhost. 559 userjspath = os.path.join(self.profile.profile, "user.js") 560 with open(userjspath) as userjsfile: 561 prefs = userjsfile.readlines() 562 prefs = [pref for pref in prefs if "network.proxy" not in pref] 563 with open(userjspath, "w") as userjsfile: 564 userjsfile.writelines(prefs) 565 566 def start_playback(self, test): 567 # creating the playback tool 568 569 playback_dir = os.path.join(here, "tooltool-manifests", "playback") 570 571 self.config.update( 572 { 573 "playback_tool": test.get("playback"), 574 "playback_version": test.get("playback_version", "4.0.4"), 575 "playback_files": [ 576 os.path.join(playback_dir, test.get("playback_pageset_manifest")) 577 ], 578 } 579 ) 580 581 LOG.info("test uses playback tool: %s " % self.config["playback_tool"]) 582 583 self.playback = get_playback(self.config) 584 585 # let's start it! 586 self.playback.start() 587 588 self.log_recording_dates(test) 589 590 def _init_gecko_profiling(self, test): 591 LOG.info("initializing gecko profiler") 592 upload_dir = os.getenv("MOZ_UPLOAD_DIR") 593 if not upload_dir: 594 LOG.critical("Profiling ignored because MOZ_UPLOAD_DIR was not set") 595 else: 596 self.gecko_profiler = GeckoProfile(upload_dir, self.config, test) 597 598 def disable_non_local_connections(self): 599 # For Firefox we need to set MOZ_DISABLE_NONLOCAL_CONNECTIONS=1 env var before startup 600 # when testing release builds from mozilla-beta/release. This is because of restrictions 601 # on release builds that require webextensions to be signed unless this env var is set 602 LOG.info("setting MOZ_DISABLE_NONLOCAL_CONNECTIONS=1") 603 os.environ["MOZ_DISABLE_NONLOCAL_CONNECTIONS"] = "1" 604 605 def enable_non_local_connections(self): 606 # pageload tests need to be able to access non-local connections via mitmproxy 607 LOG.info("setting MOZ_DISABLE_NONLOCAL_CONNECTIONS=0") 608 os.environ["MOZ_DISABLE_NONLOCAL_CONNECTIONS"] = "0" 609 610 611class PerftestAndroid(Perftest): 612 """Mixin class for Android-specific Perftest subclasses.""" 613 614 def setup_chrome_args(self, test): 615 """Sets up chrome/chromium cmd-line arguments. 616 617 Needs to be "implemented" here to deal with Python 2 618 unittest failures. 619 """ 620 raise NotImplementedError 621 622 def get_browser_meta(self): 623 """Returns the browser name and version in a tuple (name, version). 624 625 Uses mozversion as the primary method to get this meta data and for 626 android this is the only method which exists to get this data. With android, 627 we use the installerpath attribute to determine this and this only works 628 with Firefox browsers. 629 """ 630 browser_name = None 631 browser_version = None 632 633 if self.config["app"] in self.firefox_android_apps: 634 try: 635 meta = mozversion.get_version(binary=self.installerpath) 636 browser_name = meta.get("application_name") 637 browser_version = meta.get("application_version") 638 except Exception as e: 639 LOG.warning( 640 "Failed to get android browser meta data through mozversion: %s-%s" 641 % (e.__class__.__name__, e) 642 ) 643 644 if self.config["app"] == "chrome-m": 645 # We absolutely need to determine the chrome 646 # version here so that we can select the correct 647 # chromedriver for browsertime 648 from mozdevice import ADBDeviceFactory 649 650 device = ADBDeviceFactory(verbose=True) 651 binary = "com.android.chrome" 652 653 pkg_info = device.shell_output("dumpsys package %s" % binary) 654 version_matcher = re.compile(r".*versionName=([\d.]+)") 655 for line in pkg_info.split("\n"): 656 match = version_matcher.match(line) 657 if match: 658 browser_version = match.group(1) 659 browser_name = self.config["app"] 660 # First one found is the non-system 661 # or latest version. 662 break 663 664 if not browser_version: 665 raise Exception( 666 "Could not determine version for Google Chrome for Android" 667 ) 668 669 if not browser_name: 670 LOG.warning("Could not find a browser name") 671 else: 672 LOG.info("Browser name: %s" % browser_name) 673 674 if not browser_version: 675 LOG.warning("Could not find a browser version") 676 else: 677 LOG.info("Browser version: %s" % browser_version) 678 679 return (browser_name, browser_version) 680 681 def set_reverse_port(self, port): 682 tcp_port = "tcp:{}".format(port) 683 self.device.create_socket_connection("reverse", tcp_port, tcp_port) 684 685 def set_reverse_ports(self): 686 if self.is_localhost: 687 688 # only raptor-webext uses the control server 689 if self.config.get("browsertime", False) is False: 690 LOG.info("making the raptor control server port available to device") 691 self.set_reverse_port(self.control_server.port) 692 693 if self.playback: 694 LOG.info("making the raptor playback server port available to device") 695 self.set_reverse_port(self.playback.port) 696 697 if self.benchmark: 698 LOG.info("making the raptor benchmarks server port available to device") 699 self.set_reverse_port(int(self.benchmark.port)) 700 else: 701 LOG.info("Reverse port forwarding is used only on local devices") 702 703 def build_browser_profile(self): 704 super(PerftestAndroid, self).build_browser_profile() 705 706 # Merge in the Android profile. 707 path = os.path.join(self.profile_data_dir, "raptor-android") 708 LOG.info("Merging profile: {}".format(path)) 709 self.profile.merge(path) 710 self.profile.set_preferences( 711 {"browser.tabs.remote.autostart": self.config["e10s"]} 712 ) 713 714 def clear_app_data(self): 715 LOG.info("clearing %s app data" % self.config["binary"]) 716 self.device.shell("pm clear %s" % self.config["binary"]) 717 718 def set_debug_app_flag(self): 719 # required so release apks will read the android config.yml file 720 LOG.info("setting debug-app flag for %s" % self.config["binary"]) 721 self.device.shell("am set-debug-app --persistent %s" % self.config["binary"]) 722 723 def copy_profile_to_device(self): 724 """Copy the profile to the device, and update permissions of all files.""" 725 if not self.device.is_app_installed(self.config["binary"]): 726 raise Exception("%s is not installed" % self.config["binary"]) 727 728 try: 729 LOG.info("copying profile to device: %s" % self.remote_profile) 730 self.device.rm(self.remote_profile, force=True, recursive=True) 731 self.device.push(self.profile.profile, self.remote_profile) 732 self.device.chmod(self.remote_profile, recursive=True) 733 734 except Exception: 735 LOG.error("Unable to copy profile to device.") 736 raise 737 738 def turn_on_android_app_proxy(self): 739 # for geckoview/android pageload playback we can't use a policy to turn on the 740 # proxy; we need to set prefs instead; note that the 'host' may be different 741 # than '127.0.0.1' so we must set the prefs accordingly 742 proxy_prefs = {} 743 proxy_prefs["network.proxy.type"] = 1 744 proxy_prefs["network.proxy.http"] = self.playback.host 745 proxy_prefs["network.proxy.http_port"] = self.playback.port 746 proxy_prefs["network.proxy.ssl"] = self.playback.host 747 proxy_prefs["network.proxy.ssl_port"] = self.playback.port 748 proxy_prefs["network.proxy.no_proxies_on"] = self.config["host"] 749 750 LOG.info( 751 "setting profile prefs to turn on the android app proxy: {}".format( 752 proxy_prefs 753 ) 754 ) 755 self.profile.set_preferences(proxy_prefs) 756 757 758class PerftestDesktop(Perftest): 759 """Mixin class for Desktop-specific Perftest subclasses""" 760 761 def __init__(self, *args, **kwargs): 762 super(PerftestDesktop, self).__init__(*args, **kwargs) 763 if self.config["enable_webrender"]: 764 self.config["environment"]["MOZ_ACCELERATED"] = "1" 765 766 def setup_chrome_args(self, test): 767 """Sets up chrome/chromium cmd-line arguments. 768 769 Needs to be "implemented" here to deal with Python 2 770 unittest failures. 771 """ 772 raise NotImplementedError 773 774 def desktop_chrome_args(self, test): 775 """Returns cmd line options required to run pageload tests on Desktop Chrome 776 and Chromium. Also add the cmd line options to turn on the proxy and 777 ignore security certificate errors if using host localhost, 127.0.0.1. 778 """ 779 chrome_args = ["--use-mock-keychain", "--no-default-browser-check"] 780 781 if test.get("playback", False): 782 pb_args = [ 783 "--proxy-server=%s:%d" % (self.playback.host, self.playback.port), 784 "--proxy-bypass-list=localhost;127.0.0.1", 785 "--ignore-certificate-errors", 786 ] 787 788 if not self.is_localhost: 789 pb_args[0] = pb_args[0].replace("127.0.0.1", self.config["host"]) 790 791 chrome_args.extend(pb_args) 792 793 if self.debug_mode: 794 chrome_args.extend(["--auto-open-devtools-for-tabs"]) 795 796 return chrome_args 797 798 def get_browser_meta(self): 799 """Returns the browser name and version in a tuple (name, version). 800 801 On desktop, we use mozversion but a fallback method also exists 802 for non-firefox browsers, where mozversion is known to fail. The 803 methods are OS-specific, with windows being the outlier. 804 """ 805 browser_name = None 806 browser_version = None 807 808 try: 809 meta = mozversion.get_version(binary=self.config["binary"]) 810 browser_name = meta.get("application_name") 811 browser_version = meta.get("application_version") 812 except Exception as e: 813 LOG.warning( 814 "Failed to get browser meta data through mozversion: %s-%s" 815 % (e.__class__.__name__, e) 816 ) 817 LOG.info("Attempting to get version through fallback method...") 818 819 # Fall-back method to get browser version on desktop 820 try: 821 if ( 822 "linux" in self.config["platform"] 823 or "mac" in self.config["platform"] 824 ): 825 command = [self.config["binary"], "--version"] 826 proc = mozprocess.ProcessHandler(command) 827 proc.run(timeout=10, outputTimeout=10) 828 proc.wait() 829 830 bmeta = proc.output 831 meta_re = re.compile(r"([A-z\s]+)\s+([\w.]*)") 832 if len(bmeta) != 0: 833 match = meta_re.match(bmeta[0].decode("utf-8")) 834 if match: 835 browser_name = self.config["app"] 836 browser_version = match.group(2) 837 else: 838 LOG.info("Couldn't get browser version and name") 839 else: 840 # On windows we need to use wimc to get the version 841 command = r'wmic datafile where name="{0}"'.format( 842 self.config["binary"].replace("\\", r"\\") 843 ) 844 bmeta = subprocess.check_output(command) 845 846 meta_re = re.compile(r"\s+([\d.a-z]+)\s+") 847 match = meta_re.findall(bmeta.decode("utf-8")) 848 if len(match) > 0: 849 browser_name = self.config["app"] 850 browser_version = match[-1] 851 else: 852 LOG.info("Couldn't get browser version and name") 853 except Exception as e: 854 LOG.warning( 855 "Failed to get browser meta data through fallback method: %s-%s" 856 % (e.__class__.__name__, e) 857 ) 858 859 if not browser_name: 860 LOG.warning("Could not find a browser name") 861 else: 862 LOG.info("Browser name: %s" % browser_name) 863 864 if not browser_version: 865 LOG.warning("Could not find a browser version") 866 else: 867 LOG.info("Browser version: %s" % browser_version) 868 869 return (browser_name, browser_version) 870