1# Copyright (c) 2013 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.
4import logging
5import re
6import os
7import StringIO
8
9import common
10from autotest_lib.client.common_lib import error
11from autotest_lib.server import test
12from autotest_lib.server import utils
13from autotest_lib.site_utils import test_runner_utils
14from autotest_lib.server.cros import telemetry_runner
15
16
17TELEMETRY_TIMEOUT_MINS = 60
18CHROME_SRC_ROOT = '/var/cache/chromeos-cache/distfiles/target/'
19CLIENT_CHROME_ROOT = '/usr/local/telemetry/src'
20RUN_BENCHMARK  = 'tools/perf/run_benchmark'
21
22RSA_KEY = '-i %s' % test_runner_utils.TEST_KEY_PATH
23DUT_CHROME_RESULTS_DIR = '/usr/local/telemetry/src/tools/perf'
24
25# Result Statuses
26SUCCESS_STATUS = 'SUCCESS'
27WARNING_STATUS = 'WARNING'
28FAILED_STATUS = 'FAILED'
29
30# Regex for the RESULT output lines understood by chrome buildbot.
31# Keep in sync with
32# chromium/tools/build/scripts/slave/performance_log_processor.py.
33RESULTS_REGEX = re.compile(r'(?P<IMPORTANT>\*)?RESULT '
34                           r'(?P<GRAPH>[^:]*): (?P<TRACE>[^=]*)= '
35                           r'(?P<VALUE>[\{\[]?[-\d\., ]+[\}\]]?)('
36                           r' ?(?P<UNITS>.+))?')
37HISTOGRAM_REGEX = re.compile(r'(?P<IMPORTANT>\*)?HISTOGRAM '
38                             r'(?P<GRAPH>[^:]*): (?P<TRACE>[^=]*)= '
39                             r'(?P<VALUE_JSON>{.*})(?P<UNITS>.+)?')
40
41
42def _find_chrome_root_dir():
43    # Look for chrome source root, either externally mounted, or inside
44    # the chroot.  Prefer chrome-src-internal source tree to chrome-src.
45    sources_list = ('chrome-src-internal', 'chrome-src')
46
47    dir_list = [os.path.join(CHROME_SRC_ROOT, x) for x in sources_list]
48    if 'CHROME_ROOT' in os.environ:
49        dir_list.insert(0, os.environ['CHROME_ROOT'])
50
51    for dir in dir_list:
52        if os.path.exists(dir):
53            chrome_root_dir = dir
54            break
55    else:
56        raise error.TestError('Chrome source directory not found.')
57
58    logging.info('Using Chrome source tree at %s', chrome_root_dir)
59    return os.path.join(chrome_root_dir, 'src')
60
61
62def _ensure_deps(dut, test_name):
63    """
64    Ensure the dependencies are locally available on DUT.
65
66    @param dut: The autotest host object representing DUT.
67    @param test_name: Name of the telemetry test.
68    """
69    # Get DEPs using host's telemetry.
70    chrome_root_dir = _find_chrome_root_dir()
71    format_string = ('python %s/tools/perf/fetch_benchmark_deps.py %s')
72    command = format_string % (chrome_root_dir, test_name)
73    logging.info('Getting DEPs: %s', command)
74    stdout = StringIO.StringIO()
75    stderr = StringIO.StringIO()
76    try:
77        result = utils.run(command, stdout_tee=stdout,
78                           stderr_tee=stderr)
79    except error.CmdError as e:
80        logging.debug('Error occurred getting DEPs: %s\n %s\n',
81                      stdout.getvalue(), stderr.getvalue())
82        raise error.TestFail('Error occurred while getting DEPs.')
83
84    # Download DEPs to DUT.
85    # send_file() relies on rsync over ssh. Couldn't be better.
86    stdout_str = stdout.getvalue()
87    stdout.close()
88    stderr.close()
89    for dep in stdout_str.split():
90        src = os.path.join(chrome_root_dir, dep)
91        dst = os.path.join(CLIENT_CHROME_ROOT, dep)
92        if not os.path.isfile(src):
93            raise error.TestFail('Error occurred while saving DEPs.')
94        logging.info('Copying: %s -> %s', src, dst)
95        try:
96            dut.send_file(src, dst)
97        except:
98            raise error.TestFail('Error occurred while sending DEPs to dut.\n')
99
100
101class telemetry_Crosperf(test.test):
102    """Run one or more telemetry benchmarks under the crosperf script."""
103    version = 1
104
105    def scp_telemetry_results(self, client_ip, dut):
106        """Copy telemetry results from dut.
107
108        @param client_ip: The ip address of the DUT
109        @param dut: The autotest host object representing DUT.
110
111        @returns status code for scp command.
112        """
113        cmd=[]
114        src = ('root@%s:%s/results-chart.json' %
115               (dut.hostname if dut else client_ip, DUT_CHROME_RESULTS_DIR))
116        cmd.extend(['scp', telemetry_runner.DUT_SCP_OPTIONS, RSA_KEY, '-v',
117                    src, self.resultsdir])
118        command = ' '.join(cmd)
119
120        logging.debug('Retrieving Results: %s', command)
121        try:
122            result = utils.run(command,
123                               timeout=TELEMETRY_TIMEOUT_MINS * 60)
124            exit_code = result.exit_status
125        except Exception as e:
126            logging.error('Failed to retrieve results: %s', e)
127            raise
128
129        logging.debug('command return value: %d', exit_code)
130        return exit_code
131
132    def run_once(self, args, client_ip='', dut=None):
133        """
134        Run a single telemetry test.
135
136        @param args: A dictionary of the arguments that were passed
137                to this test.
138        @param client_ip: The ip address of the DUT
139        @param dut: The autotest host object representing DUT.
140
141        @returns A TelemetryResult instance with the results of this execution.
142        """
143        test_name = args['test']
144        test_args = ''
145        if 'test_args' in args:
146            test_args = args['test_args']
147
148        # Decide whether the test will run locally or by a remote server.
149        if args.get('run_local', 'false').lower() == 'true':
150            # The telemetry scripts will run on DUT.
151            _ensure_deps(dut, test_name)
152            format_string = ('python %s --browser=system '
153                             '--output-format=chartjson %s %s')
154            command = format_string % (os.path.join(CLIENT_CHROME_ROOT,
155                                                    RUN_BENCHMARK),
156                                       test_args, test_name)
157            runner = dut
158        else:
159            # The telemetry scripts will run on server.
160            format_string = ('python %s --browser=cros-chrome --remote=%s '
161                             '--output-format=chartjson %s %s')
162            command = format_string % (os.path.join(_find_chrome_root_dir(),
163                                                    RUN_BENCHMARK),
164                                       client_ip, test_args, test_name)
165            runner = utils
166
167        # Run the test.
168        stdout = StringIO.StringIO()
169        stderr = StringIO.StringIO()
170        try:
171            logging.info('CMD: %s', command)
172            result = runner.run(command, stdout_tee=stdout, stderr_tee=stderr,
173                                timeout=TELEMETRY_TIMEOUT_MINS*60)
174            exit_code = result.exit_status
175        except error.CmdError as e:
176            logging.debug('Error occurred executing telemetry.')
177            exit_code = e.result_obj.exit_status
178            raise error.TestFail('An error occurred while executing '
179                                 'telemetry test.')
180        finally:
181            stdout_str = stdout.getvalue()
182            stderr_str = stderr.getvalue()
183            stdout.close()
184            stderr.close()
185            logging.info('Telemetry completed with exit code: %d.'
186                         '\nstdout:%s\nstderr:%s', exit_code,
187                         stdout_str, stderr_str)
188
189        # Copy the results-chart.json file into the test_that results
190        # directory.
191        if args.get('run_local', 'false').lower() == 'true':
192            result = self.scp_telemetry_results(client_ip, dut)
193        else:
194            src_dir = os.path.dirname(os.path.join(_find_chrome_root_dir(),
195                                                   RUN_BENCHMARK))
196
197            filepath = os.path.join(src_dir, 'results-chart.json')
198            if os.path.exists(filepath):
199                command = 'cp %s %s' % (filepath, self.resultsdir)
200                result = utils.run(command)
201            else:
202                raise IOError('Missing results file: %s' % filepath)
203
204
205        return result
206