Commit cf4e4c4f authored by Zhenyao Mo's avatar Zhenyao Mo Committed by Commit Bot

Integrate power measurement test with gpu_integration_test harness.

So we can easily take advantage of devtools integration in tests. For example,
the ability to automatically turn into fullscreen video playing mode.

BUG=867155
TEST=manual
R=kbr@chromium.org

Cq-Include-Trybots: luci.chromium.try:android_optional_gpu_tests_rel;luci.chromium.try:linux_optional_gpu_tests_rel;luci.chromium.try:mac_optional_gpu_tests_rel;luci.chromium.try:win_optional_gpu_tests_rel
Change-Id: I2ed4d91ea82c3fc1308663c5d70e0f37bd50cd78
Reviewed-on: https://chromium-review.googlesource.com/1159690Reviewed-by: default avatarKenneth Russell <kbr@chromium.org>
Commit-Queue: Zhenyao Mo <zmo@chromium.org>
Cr-Commit-Position: refs/heads/master@{#589965}
parent f200b4d4
...@@ -19,6 +19,7 @@ An easy way to use the APIs are: ...@@ -19,6 +19,7 @@ An easy way to use the APIs are:
5 seconds, call AnalyzeIPGLogFile(skip_in_sec=5). 5 seconds, call AnalyzeIPGLogFile(skip_in_sec=5).
""" """
import json
import logging import logging
import os import os
import subprocess import subprocess
...@@ -36,17 +37,17 @@ def LocateIPG(): ...@@ -36,17 +37,17 @@ def LocateIPG():
return gadget_path return gadget_path
return None return None
def GenerateIPGLogFilename(prefix='PowerLog', dir=None, current_run=1, def GenerateIPGLogFilename(log_prefix='PowerLog', log_dir=None, current_run=1,
total_runs=1, timestamp=False): total_runs=1, timestamp=False):
# If all args take default value, it is the IPG's default log path. # If all args take default value, it is the IPG's default log path.
dir = dir or os.getcwd() log_dir = log_dir or os.getcwd()
dir = os.path.abspath(dir) log_dir = os.path.abspath(log_dir)
if total_runs > 1: if total_runs > 1:
prefix = "%s_%d_%d" % (prefix, current_run, total_runs) log_prefix = "%s_%d_%d" % (log_prefix, current_run, total_runs)
if timestamp: if timestamp:
now = datetime.datetime.now() now = datetime.datetime.now()
prefix = "%s_%s" % (prefix, now.strftime('%Y%m%d%H%M%S')) log_prefix = "%s_%s" % (log_prefix, now.strftime('%Y%m%d%H%M%S'))
return os.path.join(dir, prefix + '.csv') return os.path.join(log_dir, log_prefix + '.csv')
def RunIPG(duration_in_s=60, resolution_in_ms=100, logfile=None): def RunIPG(duration_in_s=60, resolution_in_ms=100, logfile=None):
intel_power_gadget_path = LocateIPG() intel_power_gadget_path = LocateIPG()
...@@ -57,7 +58,7 @@ def RunIPG(duration_in_s=60, resolution_in_ms=100, logfile=None): ...@@ -57,7 +58,7 @@ def RunIPG(duration_in_s=60, resolution_in_ms=100, logfile=None):
(intel_power_gadget_path, duration_in_s, resolution_in_ms)) (intel_power_gadget_path, duration_in_s, resolution_in_ms))
if not logfile: if not logfile:
# It is not necessary but allows to print out the log path for debugging. # It is not necessary but allows to print out the log path for debugging.
logfile = GenerateIPGLogFilename(); logfile = GenerateIPGLogFilename()
command = command + (' -file %s' %logfile) command = command + (' -file %s' %logfile)
logging.debug("Running: " + command) logging.debug("Running: " + command)
try: try:
...@@ -87,7 +88,7 @@ def AnalyzeIPGLogFile(logfile=None, skip_in_sec=0): ...@@ -87,7 +88,7 @@ def AnalyzeIPGLogFile(logfile=None, skip_in_sec=0):
cols = len(tokens) cols = len(tokens)
for ii in range(0, cols): for ii in range(0, cols):
if tokens[ii].startswith('Elapsed Time'): if tokens[ii].startswith('Elapsed Time'):
col_time = ii; col_time = ii
elif tokens[ii].endswith('(Watt)'): elif tokens[ii].endswith('(Watt)'):
indices.append(ii) indices.append(ii)
labels.append(tokens[ii][:-len('(Watt)')]) labels.append(tokens[ii][:-len('(Watt)')])
...@@ -109,3 +110,52 @@ def AnalyzeIPGLogFile(logfile=None, skip_in_sec=0): ...@@ -109,3 +110,52 @@ def AnalyzeIPGLogFile(logfile=None, skip_in_sec=0):
for ii in range(0, len(indices)): for ii in range(0, len(indices)):
results[labels[ii]] = sums[ii] / samples results[labels[ii]] = sums[ii] / samples
return results return results
def ProcessResultsFromMultipleIPGRuns(logfiles, skip_in_seconds=0,
outliers=0, output_json=None):
assert len(logfiles) > 1
output = {}
summary = {}
for logfile in logfiles:
results = AnalyzeIPGLogFile(logfile, skip_in_seconds)
results['log'] = logfile
(_, filename) = os.path.split(logfile)
(core, _) = os.path.splitext(filename)
prefix = 'PowerLog_'
if core.startswith(prefix):
core = core[len(prefix):]
output[core] = results
for key in results:
if key == 'samples' or key == 'log':
continue
if not key in summary:
summary[key] = [results[key]]
else:
summary[key].append(results[key])
for key in summary:
data = summary[key]
assert data and len(data) > 1
n = len(data)
if outliers > 0:
assert outliers * 2 < n
data.sort()
data = data[outliers:(n - outliers)]
n = len(data)
logging.debug('%s: valid samples = %d', key, n)
mean = sum(data) / float(n)
ss = sum((x - mean) ** 2 for x in data)
stdev = (ss / float(n)) ** 0.5
summary[key] = {
'mean': mean,
'stdev': stdev,
}
output['summary'] = summary
if output_json:
json_file = open(output_json, 'w')
json_file.write(json.dumps(output, indent=4))
json_file.close()
return summary
...@@ -16,7 +16,7 @@ python measure_power_win_intel.py --browser=canary --duration=10 --delay=5 ...@@ -16,7 +16,7 @@ python measure_power_win_intel.py --browser=canary --duration=10 --delay=5
--extra-browser-args="--no-sandbox --disable-features=UseSurfaceLayerForVideo" --extra-browser-args="--no-sandbox --disable-features=UseSurfaceLayerForVideo"
""" """
import ipg_utils from gpu_tests import ipg_utils
import logging import logging
import os import os
import shutil import shutil
...@@ -27,14 +27,14 @@ import time ...@@ -27,14 +27,14 @@ import time
import optparse import optparse
CHROME_STABLE_PATH = ( CHROME_STABLE_PATH = (
"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe") r"C:\Program Files (x86)\Google\Chrome\Application\chrome.exe")
CHROME_BETA_PATH = ( CHROME_BETA_PATH = (
"C:\Program Files (x86)\Google\Chrome Beta\Application\chrome.exe") r"C:\Program Files (x86)\Google\Chrome Beta\Application\chrome.exe")
CHROME_DEV_PATH = ( CHROME_DEV_PATH = (
"C:\Program Files (x86)\Google\Chrome Dev\Application\chrome.exe") r"C:\Program Files (x86)\Google\Chrome Dev\Application\chrome.exe")
# The following two paths are relative to the LOCALAPPDATA # The following two paths are relative to the LOCALAPPDATA
CHROME_CANARY_PATH = "Google\Chrome SxS\Application\chrome.exe" CHROME_CANARY_PATH = r"Google\Chrome SxS\Application\chrome.exe"
CHROMIUM_PATH = "Chromium\Application\chrome.exe" CHROMIUM_PATH = r"Chromium\Application\chrome.exe"
SUPPORTED_BROWSERS = ['stable', 'beta', 'dev', 'canary', 'chromium'] SUPPORTED_BROWSERS = ['stable', 'beta', 'dev', 'canary', 'chromium']
...@@ -55,7 +55,7 @@ def LocateBrowser(options_browser): ...@@ -55,7 +55,7 @@ def LocateBrowser(options_browser):
else: else:
logging.warning("Invalid value for --browser") logging.warning("Invalid value for --browser")
logging.warning( logging.warning(
"Supported values: %s, or a full path to a browser executable." % "Supported values: %s, or a full path to a browser executable.",
", ".join(SUPPORTED_BROWSERS)) ", ".join(SUPPORTED_BROWSERS))
return None return None
if not os.path.exists(browser): if not os.path.exists(browser):
...@@ -94,7 +94,7 @@ def MeasurePowerOnce(browser, logfile, duration, delay, resolution, url, ...@@ -94,7 +94,7 @@ def MeasurePowerOnce(browser, logfile, duration, delay, resolution, url,
time.sleep(0.05) time.sleep(0.05)
try: try:
shutil.rmtree(user_data_dir) shutil.rmtree(user_data_dir)
except Exception as err: except Exception:
logging.warning("Failed to remove temporary folder: " + user_data_dir) logging.warning("Failed to remove temporary folder: " + user_data_dir)
logging.warning("Please kill browser and remove it manually to avoid leak") logging.warning("Please kill browser and remove it manually to avoid leak")
results = ipg_utils.AnalyzeIPGLogFile(logfile, delay) results = ipg_utils.AnalyzeIPGLogFile(logfile, delay)
...@@ -151,7 +151,7 @@ def main(argv): ...@@ -151,7 +151,7 @@ def main(argv):
for run in range(0, options.repeat): for run in range(0, options.repeat):
logfile = ipg_utils.GenerateIPGLogFilename(log_prefix, options.logdir, logfile = ipg_utils.GenerateIPGLogFilename(log_prefix, options.logdir,
run, options.repeat, True) run, options.repeat, True)
logging.info("Iteration #%d out of %d" % (run, options.repeat)) logging.info("Iteration #%d out of %d", run, options.repeat)
results = MeasurePowerOnce(browser, logfile, options.duration, results = MeasurePowerOnce(browser, logfile, options.duration,
options.delay, options.resolution, options.url, options.delay, options.resolution, options.url,
options.extra_browser_args) options.extra_browser_args)
......
# Copyright 2018 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""This script only works on Windows with Intel CPU. Intel Power Gadget needs
to be installed on the machine before this script works. The software can be
downloaded from:
https://software.intel.com/en-us/articles/intel-power-gadget-20
"""
from gpu_tests import gpu_integration_test
from gpu_tests import ipg_utils
from gpu_tests.gpu_test_expectations import GpuTestExpectations
import os
import sys
# There are no expectations for power_measurement
class PowerMeasurementExpectations(GpuTestExpectations):
def SetExpectations(self):
pass
class PowerMeasurementIntegrationTest(gpu_integration_test.GpuIntegrationTest):
@classmethod
def Name(cls):
return 'power'
@classmethod
def AddCommandlineArgs(cls, parser):
parser.add_option("--duration", default=60, type="int",
help="specify how many seconds Intel Power Gadget "
"measures. By default, 60 seconds is selected.")
parser.add_option("--delay", default=10, type="int",
help="specify how many seconds we skip in the data "
"Intel Power Gadget collects. This time is for starting "
"video play, switching to fullscreen mode, etc. "
"By default, 10 seconds is selected.")
parser.add_option("--resolution", default=100, type="int",
help="specify how often Intel Power Gadget samples "
"data in milliseconds. By default, 100 ms is selected.")
parser.add_option("--url",
help="specify the webpage URL the browser launches with.")
parser.add_option("--fullscreen", action="store_true", default=False,
help="specify if the browser goes to fullscreen mode "
"automatically, specifically if there is a single video "
"element in the page, switch it to fullsrceen mode.")
parser.add_option("--logdir",
help="Speficy where the Intel Power Gadget log file "
"should be stored. If specified, the log file name will "
"include a timestamp. If not specified, the log file "
"will be PowerLog.csv at the current dir and will be "
"overwritten at next run.")
parser.add_option("--repeat", default=1, type="int",
help="specify how many times to repreat the measurement. "
"By default, measure only once. If measure more than "
"once, between each measurement, browser restarts.")
parser.add_option("--outliers", default=0, type="int",
help="if a test is repeated multiples and outliers is "
"set to N, then N smallest results and N largest results "
"are discarded before computing mean and stdev.")
@classmethod
def GenerateGpuTests(cls, options):
yield ('url', options.url, (options.repeat, options.outliers,
options.fullscreen, options.logdir,
options.duration, options.delay,
options.resolution))
@classmethod
def SetUpProcess(cls):
super(cls, PowerMeasurementIntegrationTest).SetUpProcess()
cls.CustomizeBrowserArgs([
'--autoplay-policy=no-user-gesture-required'
])
cls.StartBrowser()
def RunActualGpuTest(self, test_path, *args):
ipg_path = ipg_utils.LocateIPG()
if not ipg_path:
self.fail("Fail to locate Intel Power Gadget")
repeat = args[0]
outliers = args[1]
fullscreen = args[2]
ipg_logdir = args[3]
ipg_duration = args[4]
ipg_delay = args[5]
ipg_resolution = args[6]
print ""
print "Total iterations: ", repeat
logfiles = []
for iteration in range(repeat):
run_label = "Iteration_%d" % iteration
print run_label
if test_path:
self.tab.action_runner.Navigate(test_path)
if fullscreen:
# TODO(zmo): implement fullscreen mode.
pass
logfile = None
if ipg_logdir:
if not os.path.isdir(ipg_logdir):
self.fail("Folder " + ipg_logdir + " doesn't exist")
logfile = ipg_utils.GenerateIPGLogFilename(log_dir=ipg_logdir,
timestamp=True)
ipg_utils.RunIPG(ipg_duration + ipg_delay, ipg_resolution, logfile)
logfiles.append(logfile)
if repeat > 1 and iteration < repeat - 1:
self.StopBrowser()
self.StartBrowser()
if repeat == 1:
results = ipg_utils.AnalyzeIPGLogFile(logfiles[0], ipg_delay)
print "Results: ", results
else:
json_path = None
if ipg_logdir:
json_path = os.path.join(ipg_logdir, "output.json")
print "Results saved in ", json_path
summary = ipg_utils.ProcessResultsFromMultipleIPGRuns(
logfiles, ipg_delay, outliers, json_path)
print 'Summary: ', summary
@classmethod
def _CreateExpectations(cls):
return PowerMeasurementExpectations()
def load_tests(loader, tests, pattern):
del loader, tests, pattern # Unused.
return gpu_integration_test.LoadAllTestsInModule(sys.modules[__name__])
...@@ -146,6 +146,7 @@ class VerifyTestExpectations(unittest.TestCase): ...@@ -146,6 +146,7 @@ class VerifyTestExpectations(unittest.TestCase):
'info_collection_test.InfoCollectionExpectations', 'info_collection_test.InfoCollectionExpectations',
'maps_expectations.MapsExpectations', 'maps_expectations.MapsExpectations',
'pixel_expectations.PixelExpectations', 'pixel_expectations.PixelExpectations',
'power_measurement_integration_test.PowerMeasurementExpectations',
'screenshot_sync_expectations.ScreenshotSyncExpectations', 'screenshot_sync_expectations.ScreenshotSyncExpectations',
'trace_test_expectations.TraceTestExpectations', 'trace_test_expectations.TraceTestExpectations',
'webgl_conformance_expectations.WebGLConformanceExpectations', 'webgl_conformance_expectations.WebGLConformanceExpectations',
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment