| #!/usr/bin/env python3 |
| |
| # Copyright 2021 The Chromium Authors |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import argparse |
| import logging |
| import typing |
| import os |
| import datetime |
| |
| from driver import DriverContext |
| import scenarios |
| import browsers |
| import plug |
| import utils |
| |
| def IterScenarios( |
| scenario_names: typing.List[str], |
| browser_driver_factory: typing.Callable[[], browsers.BrowserDriver], |
| **kwargs): |
| for scenario_and_browser_name in scenario_names: |
| scenario_name, _, browser_name = scenario_and_browser_name.partition(':') |
| browser_name, _, variation = browser_name.partition(':') |
| browser_driver = browser_driver_factory(browser_name, variation) |
| scenario_driver = scenarios.MakeScenarioDriver(scenario_name, |
| browser_driver, **kwargs) |
| if scenario_driver is None: |
| logging.error(f"Skipping invalid scenario {scenario_and_browser_name}.") |
| else: |
| yield scenario_driver |
| |
| |
| def main(): |
| parser = argparse.ArgumentParser(description='Runs browser power benchmarks') |
| parser.add_argument("--output_dir", help="Output dir") |
| parser.add_argument('--no-checks', |
| dest='no_checks', |
| action='store_true', |
| help="Invalid environment doesn't throw") |
| parser.add_argument( |
| '--skip-wait-for-battery-not-full', |
| dest='wait_for_battery_not_full', |
| action='store_false', |
| help=("Skip waiting until the battery isn't full before recording a " |
| "scenario (for debugging only)")) |
| mode_group = parser.add_mutually_exclusive_group() |
| mode_group.add_argument( |
| '--tracing_mode', |
| dest='tracing_mode', |
| action='store_true', |
| help="Grab a trace instead of a profile or benchmark.") |
| |
| # Profile related arguments |
| mode_group.add_argument( |
| '--profile_mode', |
| dest='profile_mode', |
| action='store', |
| choices=["wakeups", "cpu_time"], |
| help="Profile the application in one of two modes: wakeups, cpu_time.") |
| parser.add_argument("--power_sampler", |
| help="Path to power sampler binary", |
| required=True) |
| parser.add_argument( |
| '--scenarios', |
| dest='scenarios', |
| action='store', |
| required=True, |
| nargs='+', |
| help="List of scenarios and browsers to run in the format" |
| "<scenario_name>:<browser_name>, e.g. idle_on_wiki:safari") |
| parser.add_argument('--meet-meeting-id', |
| dest='meet_meeting_id', |
| action='store', |
| help='The meeting ID to use for the Meet benchmarks') |
| parser.add_argument( |
| '--chrome-user-dir', |
| dest='chrome_user_dir', |
| action='store', |
| help='The user data dir to pass to Chrome via --user-data-dir') |
| |
| parser.add_argument('--verbose', |
| action='store_true', |
| default=False, |
| help='Print verbose output.') |
| |
| parser.add_argument( |
| "--brightness_level", |
| type=int, |
| required=False, |
| # This is the average brightness from UMA data. |
| default=65, |
| help="Desired brightness level.") |
| |
| # If an ip is provided for the Kasa switch it needs to be fully set up |
| # (see plug.py). It will be used to keep the machine charged between |
| # scenarios. |
| parser.add_argument( |
| "--kasa_switch_ip", |
| required=False, |
| help="IP address of the kasa power switch controlling the current device." |
| ) |
| |
| parser.add_argument('--extra-command-line', |
| dest='extra_command_line', |
| action='append', |
| help="Multiple values are suported.") |
| |
| parser.add_argument('--tag', |
| dest='tag', |
| default="", |
| action='store', |
| help='Tag to be added to metada to identify run.') |
| |
| args = parser.parse_args() |
| |
| if args.verbose: |
| log_level = logging.DEBUG |
| else: |
| log_level = logging.INFO |
| logging.basicConfig(format='%(asctime)s %(levelname)s: %(message)s', |
| level=log_level) |
| |
| output_dir = args.output_dir |
| if not output_dir: |
| output_dir = os.path.join("output", |
| datetime.datetime.now().strftime("%Y%m%d-%H%M%S")) |
| |
| kasa_plug_controller = None |
| if args.kasa_switch_ip: |
| kasa_plug_controller = plug.get_plug_controller(args.kasa_switch_ip) |
| # Turn off power to pass environment checks below. |
| kasa_plug_controller.turn_off() |
| |
| logging.info(f'Outputing results in {os.path.abspath(output_dir)}') |
| with DriverContext(output_dir, args.power_sampler) as driver: |
| driver.CheckEnv(not args.no_checks) |
| driver.SetMainDisplayBrightness(args.brightness_level) |
| |
| # Measure or Profile all defined scenarios. |
| def BrowserFactory(browser_name, variation): |
| return browsers.MakeBrowserDriver( |
| browser_name, |
| variation, |
| chrome_user_dir=args.chrome_user_dir, |
| output_dir=output_dir, |
| tracing_mode=args.tracing_mode, |
| extra_command_line=args.extra_command_line) |
| |
| for scenario in IterScenarios(args.scenarios, |
| BrowserFactory, |
| meet_meeting_id=args.meet_meeting_id): |
| |
| scenario.tag = args.tag |
| |
| if kasa_plug_controller: |
| kasa_plug_controller.charge_or_discharge_to(80) |
| |
| if args.tracing_mode: |
| logging.info(f'Tracing scenario {scenario.name} ...') |
| driver.Trace(scenario) |
| elif args.profile_mode: |
| logging.info(f'Profiling scenario {scenario.name} ...') |
| driver.Profile(scenario, profile_mode=args.profile_mode) |
| else: |
| # This returns immediately after an IOPMPowerSource notification, which |
| # is required for power measurements that cover precisely the benchmark |
| # interval (if the benchmark starts n seconds after an IOPMPowerSource |
| # notification, power measurements will implicitly include these n |
| # seconds during which the benchmark wasn't running). |
| if args.wait_for_battery_not_full: |
| driver.WaitBatteryNotFull() |
| |
| logging.info(f'Recording scenario {scenario.name} ...') |
| driver.Record(scenario) |
| |
| |
| if __name__ == "__main__": |
| main() |