blob: f29b072d4153572d72c027e1453a1c7413d647be [file] [log] [blame]
# Copyright 2020 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Implementation of the graphics_TraceReplayExtended server test."""
import logging
import os
import threading
import time
from autotest_lib.client.common_lib import error
from autotest_lib.server import test
from autotest_lib.server.cros.graphics import graphics_power
from autotest_lib.server.site_tests.tast import tast
class TastManagerThread(threading.Thread):
"""Thread for running a local tast test from an autotest server test."""
def __init__(self,
host,
tast_instance,
client_test,
max_duration_minutes,
build_bundle,
varslist=None,
command_args=None):
"""Initializes the thread.
Args:
host: An autotest host instance.
tast_instance: An instance of the tast.tast() class.
client_test: String identifying which tast test to run.
max_duration_minutes: Float defining the maximum running time of the
managed sub-test.
build_bundle: String defining which tast test bundle to build and
query for the client_test.
varslist: list of strings that define dynamic variables made
available to tast tests at runtime via `tast run -var=name=value
...`. Each string should be formatted as 'name=value'.
command_args: list of strings that are passed as args to the `tast
run` command.
"""
super(TastManagerThread, self).__init__(name=__name__)
self.tast = tast_instance
self.tast.initialize(
host=host,
test_exprs=[client_test],
ignore_test_failures=True,
max_run_sec=max_duration_minutes * 60,
command_args=command_args if command_args else [],
build_bundle=build_bundle,
varslist=varslist)
def run(self):
logging.info('Started thread: %s', self.__class__.__name__)
self.tast.run_once()
class graphics_TraceReplayExtended(test.test):
"""Autotest server test for running repeated trace replays.
This test simultaneously initiates system performance logging and extended
trace replay processes on a target host, and parses their test results for
combined analysis and reporting.
"""
version = 1
@staticmethod
def _initialize_dir_on_host(host, directory):
"""Initialize a directory to a consistent (empty) state on the host.
Args:
host: An autotest host instance.
directory: String defining the location of the directory to
initialize.
Raises:
TestFail: If the directory cannot be initialized.
"""
try:
host.run('rm -r %(0)s 2>/dev/null || true; ! test -d %(0)s' %
{'0': directory})
host.run('mkdir -p %s' % directory)
except (error.AutotestHostRunCmdError, error.AutoservRunError) as err:
logging.exception(err)
raise error.TestFail(
'Failed to initialize directory "%s" on the test host' %
directory)
@staticmethod
def _cleanup_dir_on_host(host, directory):
"""Ensure that a directory and its contents are deleted on the host.
Args:
host: An autotest host instance.
directory: String defining the location of the directory to delete.
Raises:
TestFail: If the directory remains on the host.
"""
try:
host.run('rm -r %(0)s || true; ! test -d %(0)s' % {'0': directory})
except (error.AutotestHostRunCmdError, error.AutoservRunError) as err:
logging.exception(err)
raise error.TestFail(
'Failed to cleanup directory "%s" on the test host' % directory)
def run_once(self,
host,
client_tast_test,
max_duration_minutes,
tast_build_bundle='cros',
tast_varslist=None,
tast_command_args=None):
"""Runs the test.
Args:
host: An autotest host instance.
client_tast_test: String defining which tast test to run.
max_duration_minutes: Float defining the maximum running time of the
managed sub-test.
tast_build_bundle: String defining which tast test bundle to build
and query for the client_test.
tast_varslist: list of strings that define dynamic variables made
available to tast tests at runtime via `tast run -var=name=value
...`. Each string should be formatted as 'name=value'.
tast_command_args: list of strings that are passed as args to the
`tast run` command.
"""
# Construct a suffix tag indicating which managing test is using logged
# data from the graphics_Power subtest.
trace_name = client_tast_test.split('.')[-1]
# workaround for running test locally since crrev/c/2374267 and
# crrev/i/2374267
if not tast_command_args:
tast_command_args = []
tast_command_args.extend([
'extraallowedbuckets=termina-component-testing,cros-containers-staging'
])
# Define paths of signal files for basic RPC/IPC between sub-tests.
temp_io_root = '/tmp/%s/' % self.__class__.__name__
result_dir = os.path.join(temp_io_root, 'results')
signal_running_file = os.path.join(temp_io_root, 'signal_running')
signal_checkpoint_file = os.path.join(temp_io_root, 'signal_checkpoint')
# This test is responsible for creating/deleting root and resultdir.
logging.debug('Creating temporary IPC/RPC dir: %s', temp_io_root)
self._initialize_dir_on_host(host, temp_io_root)
self._initialize_dir_on_host(host, result_dir)
# Start background system performance monitoring process on the test
# target (via an autotest client 'power_Test').
logging.debug('Connecting to autotest client on host')
graphics_power_thread = graphics_power.GraphicsPowerThread(
host=host,
max_duration_minutes=max_duration_minutes,
test_tag='Trace' + '.' + trace_name,
pdash_note='',
result_dir=result_dir,
signal_running_file=signal_running_file,
signal_checkpoint_file=signal_checkpoint_file)
graphics_power_thread.start()
logging.info('Waiting for graphics_Power subtest to initialize...')
try:
graphics_power_thread.wait_until_running(timeout=120)
except Exception as err:
logging.exception(err)
raise error.TestFail(
'An error occured during graphics_Power subtest initialization')
logging.info('The graphics_Power subtest was properly initialized')
# Start repeated trace replay process on the test target (via a tast
# local test).
logging.info('Running Tast test: %s', client_tast_test)
tast_outputdir = os.path.join(self.outputdir, 'tast')
if not os.path.exists(tast_outputdir):
logging.debug('Creating tast outputdir: %s', tast_outputdir)
os.makedirs(tast_outputdir)
if not tast_varslist:
tast_varslist = []
tast_varslist.extend([
'graphics.TraceReplayExtended.resultDir=' + result_dir,
'graphics.TraceReplayExtended.signalRunningFile=' +
signal_running_file,
'graphics.TraceReplayExtended.signalCheckpointFile=' +
signal_checkpoint_file,
])
tast_instance = tast.tast(
job=self.job, bindir=self.bindir, outputdir=tast_outputdir)
tast_manager_thread = TastManagerThread(
host,
tast_instance,
client_tast_test,
max_duration_minutes,
tast_build_bundle,
varslist=tast_varslist,
command_args=tast_command_args)
tast_manager_thread.start()
# Block until both subtests finish.
threads = [graphics_power_thread, tast_manager_thread]
stop_attempts = 0
while threads:
# TODO(ryanneph): Move stop signal emission to tast test instance.
if (not tast_manager_thread.is_alive() and
graphics_power_thread.is_alive() and stop_attempts < 1):
logging.info('Attempting to stop graphics_Power thread')
graphics_power_thread.stop(timeout=0)
stop_attempts += 1
# Raise test failure if graphics_Power thread ends before tast test.
if (not graphics_power_thread.is_alive() and
tast_manager_thread.is_alive()):
raise error.TestFail(
'The graphics_Power subtest ended too soon.')
for thread in list(threads):
if not thread.is_alive():
logging.info('Thread "%s" has ended',
thread.__class__.__name__)
threads.remove(thread)
time.sleep(1)
client_result_dir = os.path.join(self.outputdir, 'client_results')
logging.info('Saving client results to %s', client_result_dir)
host.get_file(result_dir, client_result_dir)
# Ensure the host filesystem is clean for the next test.
self._cleanup_dir_on_host(host, result_dir)
self._cleanup_dir_on_host(host, temp_io_root)
# TODO(ryanneph): Implement results parsing/analysis/reporting