зеркало из https://github.com/mozilla/gecko-dev.git
264 строки
10 KiB
Python
264 строки
10 KiB
Python
#!/usr/bin/env python
|
|
|
|
# This Source Code Form is subject to the terms of the Mozilla Public
|
|
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
import sys
|
|
import os
|
|
from os import path
|
|
import time
|
|
import datetime
|
|
import argparse
|
|
import platform
|
|
import subprocess
|
|
|
|
TOP_DIR = path.join("..", "..")
|
|
GUARD_TIME = 10
|
|
HEARTBEAT_DEFAULT_WINDOW_SIZE = 20
|
|
# Use a larger window sizes to reduce or prevent writing log files until benchmark completion
|
|
# (profiler name, window size)
|
|
# These categories need to be kept aligned with ProfilerCategory in components/profile_traits/time.rs
|
|
HEARTBEAT_PROFILER_CATEGORIES = [
|
|
("Compositing", HEARTBEAT_DEFAULT_WINDOW_SIZE),
|
|
("LayoutPerform", HEARTBEAT_DEFAULT_WINDOW_SIZE),
|
|
("LayoutStyleRecalc", HEARTBEAT_DEFAULT_WINDOW_SIZE),
|
|
# ("LayoutTextShaping", HEARTBEAT_DEFAULT_WINDOW_SIZE),
|
|
("LayoutRestyleDamagePropagation", HEARTBEAT_DEFAULT_WINDOW_SIZE),
|
|
("LayoutNonIncrementalReset", HEARTBEAT_DEFAULT_WINDOW_SIZE),
|
|
("LayoutSelectorMatch", HEARTBEAT_DEFAULT_WINDOW_SIZE),
|
|
("LayoutTreeBuilder", HEARTBEAT_DEFAULT_WINDOW_SIZE),
|
|
("LayoutDamagePropagate", HEARTBEAT_DEFAULT_WINDOW_SIZE),
|
|
("LayoutGeneratedContent", HEARTBEAT_DEFAULT_WINDOW_SIZE),
|
|
("LayoutDisplayListSorting", HEARTBEAT_DEFAULT_WINDOW_SIZE),
|
|
("LayoutFloatPlacementSpeculation", HEARTBEAT_DEFAULT_WINDOW_SIZE),
|
|
("LayoutMain", HEARTBEAT_DEFAULT_WINDOW_SIZE),
|
|
("LayoutStoreOverflow", HEARTBEAT_DEFAULT_WINDOW_SIZE),
|
|
("LayoutParallelWarmup", HEARTBEAT_DEFAULT_WINDOW_SIZE),
|
|
("LayoutDispListBuild", HEARTBEAT_DEFAULT_WINDOW_SIZE),
|
|
("NetHTTPRequestResponse", HEARTBEAT_DEFAULT_WINDOW_SIZE),
|
|
("PaintingPerTile", 50),
|
|
("PaintingPrepBuff", HEARTBEAT_DEFAULT_WINDOW_SIZE),
|
|
("Painting", HEARTBEAT_DEFAULT_WINDOW_SIZE),
|
|
("ImageDecoding", HEARTBEAT_DEFAULT_WINDOW_SIZE),
|
|
("ImageSaving", HEARTBEAT_DEFAULT_WINDOW_SIZE),
|
|
("ScriptAttachLayout", HEARTBEAT_DEFAULT_WINDOW_SIZE),
|
|
("ScriptConstellationMsg", HEARTBEAT_DEFAULT_WINDOW_SIZE),
|
|
("ScriptDevtoolsMsg", HEARTBEAT_DEFAULT_WINDOW_SIZE),
|
|
("ScriptDocumentEvent", HEARTBEAT_DEFAULT_WINDOW_SIZE),
|
|
("ScriptDomEvent", HEARTBEAT_DEFAULT_WINDOW_SIZE),
|
|
("ScriptEvaluate", HEARTBEAT_DEFAULT_WINDOW_SIZE),
|
|
("ScriptEvent", HEARTBEAT_DEFAULT_WINDOW_SIZE),
|
|
("ScriptFileRead", HEARTBEAT_DEFAULT_WINDOW_SIZE),
|
|
("ScriptImageCacheMsg", HEARTBEAT_DEFAULT_WINDOW_SIZE),
|
|
("ScriptInputEvent", HEARTBEAT_DEFAULT_WINDOW_SIZE),
|
|
("ScriptNetworkEvent", 200),
|
|
("ScriptParseHTML", HEARTBEAT_DEFAULT_WINDOW_SIZE),
|
|
("ScriptPlannedNavigation", HEARTBEAT_DEFAULT_WINDOW_SIZE),
|
|
("ScriptResize", HEARTBEAT_DEFAULT_WINDOW_SIZE),
|
|
("ScriptSetScrollState", HEARTBEAT_DEFAULT_WINDOW_SIZE),
|
|
("ScriptSetViewport", HEARTBEAT_DEFAULT_WINDOW_SIZE),
|
|
("ScriptTimerEvent", HEARTBEAT_DEFAULT_WINDOW_SIZE),
|
|
("ScriptStylesheetLoad", HEARTBEAT_DEFAULT_WINDOW_SIZE),
|
|
("ScriptUpdateReplacedElement", HEARTBEAT_DEFAULT_WINDOW_SIZE),
|
|
("ScriptWebSocketEvent", HEARTBEAT_DEFAULT_WINDOW_SIZE),
|
|
("ScriptWorkerEvent", HEARTBEAT_DEFAULT_WINDOW_SIZE),
|
|
("ScriptServiceWorkerEvent", HEARTBEAT_DEFAULT_WINDOW_SIZE),
|
|
("ScriptParseXML", HEARTBEAT_DEFAULT_WINDOW_SIZE),
|
|
("ScriptEnterFullscreen", HEARTBEAT_DEFAULT_WINDOW_SIZE),
|
|
("ScriptExitFullscreen", HEARTBEAT_DEFAULT_WINDOW_SIZE),
|
|
("ScriptWebVREvent", HEARTBEAT_DEFAULT_WINDOW_SIZE),
|
|
("ApplicationHeartbeat", 100),
|
|
]
|
|
ENERGY_READER_BIN = "energymon-file-provider"
|
|
ENERGY_READER_TEMP_OUTPUT = "energymon.txt"
|
|
SUMMARY_OUTPUT = "summary.txt"
|
|
|
|
|
|
def get_command(build_target, layout_thread_count, renderer, page, profile):
|
|
"""Get the command to execute.
|
|
"""
|
|
return path.join(TOP_DIR, "target", build_target, "servo") + \
|
|
" -p %d -o output.png -y %d %s -Z profile-script-events '%s'" % \
|
|
(profile, layout_thread_count, renderer, page)
|
|
|
|
|
|
def set_app_environment(log_dir):
|
|
"""Set environment variables to enable heartbeats.
|
|
"""
|
|
prefix = "heartbeat-"
|
|
for (profiler, window) in HEARTBEAT_PROFILER_CATEGORIES:
|
|
os.environ["SERVO_HEARTBEAT_ENABLE_" + profiler] = ""
|
|
os.environ["SERVO_HEARTBEAT_LOG_" + profiler] = path.join(log_dir, prefix + profiler + ".log")
|
|
os.environ["SERVO_HEARTBEAT_WINDOW_" + profiler] = str(window)
|
|
|
|
|
|
def start_energy_reader():
|
|
"""Energy reader writes to a file that we will poll.
|
|
"""
|
|
os.system(ENERGY_READER_BIN + " " + ENERGY_READER_TEMP_OUTPUT + "&")
|
|
|
|
|
|
def stop_energy_reader():
|
|
"""Stop the energy reader and remove its temp file.
|
|
"""
|
|
os.system("pkill -x " + ENERGY_READER_BIN)
|
|
os.remove(ENERGY_READER_TEMP_OUTPUT)
|
|
|
|
|
|
def read_energy():
|
|
"""Poll the energy reader's temp file.
|
|
"""
|
|
data = 0
|
|
with open(ENERGY_READER_TEMP_OUTPUT, "r") as em:
|
|
data = int(em.read().replace('\n', ''))
|
|
return data
|
|
|
|
|
|
def git_rev_hash():
|
|
"""Get the git revision hash.
|
|
"""
|
|
return subprocess.check_output(['git', 'rev-parse', 'HEAD']).rstrip()
|
|
|
|
|
|
def git_rev_hash_short():
|
|
"""Get the git revision short hash.
|
|
"""
|
|
return subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD']).rstrip()
|
|
|
|
|
|
def execute(base_dir, build_target, renderer, page, profile, trial, layout_thread_count):
|
|
"""Run a single execution.
|
|
"""
|
|
log_dir = path.join(base_dir, "logs_l" + str(layout_thread_count),
|
|
"trial_" + str(trial))
|
|
if os.path.exists(log_dir):
|
|
print "Log directory already exists: " + log_dir
|
|
sys.exit(1)
|
|
os.makedirs(log_dir)
|
|
|
|
set_app_environment(log_dir)
|
|
cmd = get_command(build_target, layout_thread_count, renderer, page, profile)
|
|
|
|
# Execute
|
|
start_energy_reader()
|
|
print 'sleep ' + str(GUARD_TIME)
|
|
time.sleep(GUARD_TIME)
|
|
time_start = time.time()
|
|
energy_start = read_energy()
|
|
print cmd
|
|
os.system(cmd)
|
|
energy_end = read_energy()
|
|
time_end = time.time()
|
|
stop_energy_reader()
|
|
print 'sleep ' + str(GUARD_TIME)
|
|
time.sleep(GUARD_TIME)
|
|
|
|
uj = energy_end - energy_start
|
|
latency = time_end - time_start
|
|
watts = uj / 1000000.0 / latency
|
|
# Write a file that describes this execution
|
|
with open(path.join(log_dir, SUMMARY_OUTPUT), "w") as f:
|
|
f.write("Datetime (UTC): " + datetime.datetime.utcnow().isoformat())
|
|
f.write("\nPlatform: " + platform.platform())
|
|
f.write("\nGit hash: " + git_rev_hash())
|
|
f.write("\nGit short hash: " + git_rev_hash_short())
|
|
f.write("\nRelease: " + build_target)
|
|
f.write("\nLayout threads: " + str(layout_thread_count))
|
|
f.write("\nTrial: " + str(trial))
|
|
f.write("\nCommand: " + cmd)
|
|
f.write("\nTime (sec): " + str(latency))
|
|
f.write("\nEnergy (uJ): " + str(uj))
|
|
f.write("\nPower (W): " + str(watts))
|
|
|
|
|
|
def characterize(build_target, base_dir, (min_layout_threads, max_layout_threads), renderer, page, profile, trials):
|
|
"""Run all configurations and capture results.
|
|
"""
|
|
for layout_thread_count in xrange(min_layout_threads, max_layout_threads + 1):
|
|
for trial in xrange(1, trials + 1):
|
|
execute(base_dir, build_target, renderer, page, profile, trial, layout_thread_count)
|
|
|
|
|
|
def main():
|
|
"""For this script to be useful, the following conditions are needed:
|
|
- HEARTBEAT_PROFILER_CATEGORIES should be aligned with the profiler categories in the source code.
|
|
- The "energymon" project needs to be installed to the system (libraries and the "energymon" binary).
|
|
- The "default" energymon library will be used - make sure you choose one that is useful for your system setup
|
|
when installing energymon.
|
|
- Build servo in release mode with the "energy-profiling" feature enabled (this links with the energymon lib).
|
|
"""
|
|
# Default max number of layout threads
|
|
max_layout_threads = 1
|
|
# Default benchmark
|
|
benchmark = path.join(TOP_DIR, "tests", "html", "perf-rainbow.html")
|
|
# Default renderer
|
|
renderer = ""
|
|
# Default output directory
|
|
output_dir = "heartbeat_logs"
|
|
# Default build target
|
|
build_target = "release"
|
|
# Default profile interval
|
|
profile = 60
|
|
# Default single argument
|
|
single = False
|
|
# Default number of trials
|
|
trials = 1
|
|
|
|
# Parsing the input of the script
|
|
parser = argparse.ArgumentParser(description="Characterize Servo timing and energy behavior")
|
|
parser.add_argument("-b", "--benchmark",
|
|
default=benchmark,
|
|
help="Gets the benchmark, for example \"-b http://www.example.com\"")
|
|
parser.add_argument("-d", "--debug",
|
|
action='store_true',
|
|
help="Use debug build instead of release build")
|
|
parser.add_argument("-w", "--webrender",
|
|
action='store_true',
|
|
help="Use webrender backend")
|
|
parser.add_argument("-l", "--max_layout_threads",
|
|
help="Specify the maximum number of threads for layout, for example \"-l 5\"")
|
|
parser.add_argument("-o", "--output",
|
|
help="Specify the log output directory, for example \"-o heartbeat_logs\"")
|
|
parser.add_argument("-p", "--profile",
|
|
default=60,
|
|
help="Profiler output interval, for example \"-p 60\"")
|
|
parser.add_argument("-s", "--single",
|
|
action='store_true',
|
|
help="Just run a single trial of the config provided, for example \"-s\"")
|
|
parser.add_argument("-t", "--trials",
|
|
default=1,
|
|
type=int,
|
|
help="Number of trials to run for each configuration, for example \"-t 1\"")
|
|
|
|
args = parser.parse_args()
|
|
if args.benchmark:
|
|
benchmark = args.benchmark
|
|
if args.debug:
|
|
build_target = "debug"
|
|
if args.webrender:
|
|
renderer = "-w"
|
|
if args.max_layout_threads:
|
|
max_layout_threads = int(args.max_layout_threads)
|
|
if args.output:
|
|
output_dir = args.output
|
|
if args.profile:
|
|
profile = args.profile
|
|
if args.single:
|
|
single = True
|
|
if args.trials:
|
|
trials = args.trials
|
|
|
|
if os.path.exists(output_dir):
|
|
print "Output directory already exists: " + output_dir
|
|
sys.exit(1)
|
|
os.makedirs(output_dir)
|
|
|
|
if single:
|
|
execute(output_dir, build_target, renderer, benchmark, profile, trials, max_layout_threads)
|
|
else:
|
|
characterize(build_target, output_dir, (1, max_layout_threads), renderer, benchmark, profile, trials)
|
|
|
|
if __name__ == "__main__":
|
|
main()
|