• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2020 The Chromium OS Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4"""Implementation of the graphics_TraceReplayExtended server test."""
5
6import logging
7import os
8import threading
9import time
10
11from autotest_lib.client.common_lib import error
12from autotest_lib.server import test
13from autotest_lib.server.cros.graphics import graphics_power
14from autotest_lib.server.site_tests.tast import tast
15
16
17class TastManagerThread(threading.Thread):
18    """Thread for running a local tast test from an autotest server test."""
19
20    def __init__(self,
21                 host,
22                 tast_instance,
23                 client_test,
24                 max_duration_minutes,
25                 build_bundle,
26                 varslist=None,
27                 command_args=None):
28        """Initializes the thread.
29
30        Args:
31            host: An autotest host instance.
32            tast_instance: An instance of the tast.tast() class.
33            client_test: String identifying which tast test to run.
34            max_duration_minutes: Float defining the maximum running time of the
35                managed sub-test.
36            build_bundle: String defining which tast test bundle to build and
37                query for the client_test.
38            varslist: list of strings that define dynamic variables made
39                available to tast tests at runtime via `tast run -var=name=value
40                ...`. Each string should be formatted as 'name=value'.
41            command_args: list of strings that are passed as args to the `tast
42                run` command.
43        """
44        super(TastManagerThread, self).__init__(name=__name__)
45        self.tast = tast_instance
46        self.tast.initialize(
47            host=host,
48            test_exprs=[client_test],
49            ignore_test_failures=True,
50            max_run_sec=max_duration_minutes * 60,
51            command_args=command_args if command_args else [],
52            build_bundle=build_bundle,
53            varslist=varslist)
54
55    def run(self):
56        logging.info('Started thread: %s', self.__class__.__name__)
57        self.tast.run_once()
58
59
60class graphics_TraceReplayExtended(test.test):
61    """Autotest server test for running repeated trace replays.
62
63    This test simultaneously initiates system performance logging and extended
64    trace replay processes on a target host, and parses their test results for
65    combined analysis and reporting.
66    """
67    version = 1
68
69    @staticmethod
70    def _initialize_dir_on_host(host, directory):
71        """Initialize a directory to a consistent (empty) state on the host.
72
73        Args:
74            host: An autotest host instance.
75            directory: String defining the location of the directory to
76                initialize.
77
78        Raises:
79            TestFail: If the directory cannot be initialized.
80        """
81        try:
82            host.run('rm -r %(0)s 2>/dev/null || true; ! test -d %(0)s' %
83                     {'0': directory})
84            host.run('mkdir -p %s' % directory)
85        except (error.AutotestHostRunCmdError, error.AutoservRunError) as err:
86            logging.exception(err)
87            raise error.TestFail(
88                'Failed to initialize directory "%s" on the test host' %
89                directory)
90
91    @staticmethod
92    def _cleanup_dir_on_host(host, directory):
93        """Ensure that a directory and its contents are deleted on the host.
94
95        Args:
96            host: An autotest host instance.
97            directory: String defining the location of the directory to delete.
98
99        Raises:
100            TestFail: If the directory remains on the host.
101        """
102        try:
103            host.run('rm -r %(0)s || true; ! test -d %(0)s' % {'0': directory})
104        except (error.AutotestHostRunCmdError, error.AutoservRunError) as err:
105            logging.exception(err)
106            raise error.TestFail(
107                'Failed to cleanup directory "%s" on the test host' % directory)
108
109    def run_once(self,
110                 host,
111                 client_tast_test,
112                 max_duration_minutes,
113                 tast_build_bundle='cros',
114                 tast_varslist=None,
115                 tast_command_args=None):
116        """Runs the test.
117
118        Args:
119            host: An autotest host instance.
120            client_tast_test: String defining which tast test to run.
121            max_duration_minutes: Float defining the maximum running time of the
122                managed sub-test.
123            tast_build_bundle: String defining which tast test bundle to build
124                and query for the client_test.
125            tast_varslist: list of strings that define dynamic variables made
126                available to tast tests at runtime via `tast run -var=name=value
127                ...`. Each string should be formatted as 'name=value'.
128            tast_command_args: list of strings that are passed as args to the
129                `tast run` command.
130        """
131        # Construct a suffix tag indicating which managing test is using logged
132        # data from the graphics_Power subtest.
133        trace_name = client_tast_test.split('.')[-1]
134
135        # workaround for running test locally since crrev/c/2374267 and
136        # crrev/i/2374267
137        if not tast_command_args:
138            tast_command_args = []
139        tast_command_args.extend([
140                'extraallowedbuckets=termina-component-testing,cros-containers-staging'
141        ])
142
143        # Define paths of signal files for basic RPC/IPC between sub-tests.
144        temp_io_root = '/tmp/%s/' % self.__class__.__name__
145        result_dir = os.path.join(temp_io_root, 'results')
146        signal_running_file = os.path.join(temp_io_root, 'signal_running')
147        signal_checkpoint_file = os.path.join(temp_io_root, 'signal_checkpoint')
148
149        # This test is responsible for creating/deleting root and resultdir.
150        logging.debug('Creating temporary IPC/RPC dir: %s', temp_io_root)
151        self._initialize_dir_on_host(host, temp_io_root)
152        self._initialize_dir_on_host(host, result_dir)
153
154        # Start background system performance monitoring process on the test
155        # target (via an autotest client 'power_Test').
156        logging.debug('Connecting to autotest client on host')
157        graphics_power_thread = graphics_power.GraphicsPowerThread(
158            host=host,
159            max_duration_minutes=max_duration_minutes,
160            test_tag='Trace' + '.' + trace_name,
161            pdash_note='',
162            result_dir=result_dir,
163            signal_running_file=signal_running_file,
164            signal_checkpoint_file=signal_checkpoint_file)
165        graphics_power_thread.start()
166
167        logging.info('Waiting for graphics_Power subtest to initialize...')
168        try:
169            graphics_power_thread.wait_until_running(timeout=120)
170        except Exception as err:
171            logging.exception(err)
172            raise error.TestFail(
173                'An error occured during graphics_Power subtest initialization')
174        logging.info('The graphics_Power subtest was properly initialized')
175
176        # Start repeated trace replay process on the test target (via a tast
177        # local test).
178        logging.info('Running Tast test: %s', client_tast_test)
179        tast_outputdir = os.path.join(self.outputdir, 'tast')
180        if not os.path.exists(tast_outputdir):
181            logging.debug('Creating tast outputdir: %s', tast_outputdir)
182            os.makedirs(tast_outputdir)
183
184        if not tast_varslist:
185            tast_varslist = []
186        tast_varslist.extend([
187            'graphics.TraceReplayExtended.resultDir=' + result_dir,
188            'graphics.TraceReplayExtended.signalRunningFile=' +
189            signal_running_file,
190            'graphics.TraceReplayExtended.signalCheckpointFile=' +
191            signal_checkpoint_file,
192        ])
193
194        tast_instance = tast.tast(
195            job=self.job, bindir=self.bindir, outputdir=tast_outputdir)
196        tast_manager_thread = TastManagerThread(
197            host,
198            tast_instance,
199            client_tast_test,
200            max_duration_minutes,
201            tast_build_bundle,
202            varslist=tast_varslist,
203            command_args=tast_command_args)
204        tast_manager_thread.start()
205
206        # Block until both subtests finish.
207        threads = [graphics_power_thread, tast_manager_thread]
208        stop_attempts = 0
209        while threads:
210            # TODO(ryanneph): Move stop signal emission to tast test instance.
211            if (not tast_manager_thread.is_alive() and
212                    graphics_power_thread.is_alive() and stop_attempts < 1):
213                logging.info('Attempting to stop graphics_Power thread')
214                graphics_power_thread.stop(timeout=0)
215                stop_attempts += 1
216
217            # Raise test failure if graphics_Power thread ends before tast test.
218            if (not graphics_power_thread.is_alive() and
219                    tast_manager_thread.is_alive()):
220                raise error.TestFail(
221                    'The graphics_Power subtest ended too soon.')
222
223            for thread in list(threads):
224                if not thread.is_alive():
225                    logging.info('Thread "%s" has ended',
226                                 thread.__class__.__name__)
227                    threads.remove(thread)
228            time.sleep(1)
229
230        client_result_dir = os.path.join(self.outputdir, 'client_results')
231        logging.info('Saving client results to %s', client_result_dir)
232        host.get_file(result_dir, client_result_dir)
233
234        # Ensure the host filesystem is clean for the next test.
235        self._cleanup_dir_on_host(host, result_dir)
236        self._cleanup_dir_on_host(host, temp_io_root)
237
238        # TODO(ryanneph): Implement results parsing/analysis/reporting
239