#!/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()