# 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