1# Copyright 2018 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
5import logging
6import numpy
7import os
8
9from autotest_lib.client.bin import utils
10from autotest_lib.client.common_lib.cros import tpm_utils
11from autotest_lib.server import test, autotest
12
13CLIENT_TEST_NAME = 'platform_InitLoginPerf'
14STAGE_OOBE = 0
15STAGE_REGULAR = 1
16STAGE_NAME = ['oobe', 'regular']
17BENCHMARKS = {
18        'initial_login': {'stage': STAGE_OOBE,
19                          'name': 'login-duration',
20                          'display': '1stLogin',
21                          'units': 'seconds',
22                          'upload': True},
23        'regular_login': {'stage': STAGE_REGULAR,
24                          'name': 'login-duration',
25                          'display': 'RegLogin',
26                          'units': 'seconds',
27                          'upload': True},
28        'prepare_attestation': {'stage': STAGE_OOBE,
29                                'name': 'attestation-duration',
30                                'display': 'PrepAttn',
31                                'units': 'seconds',
32                                'upload': True},
33        }
34
35class platform_InitLoginPerfServer(test.test):
36    """Test to exercise and gather perf data for initialization and login."""
37
38    version = 1
39
40    def initialize(self):
41        """Run before the first iteration."""
42        self.perf_results = {}
43        for bmname in BENCHMARKS:
44            self.perf_results[bmname] = []
45
46    def stage_args(self, stage):
47        """Build arguments for the client-side test.
48
49        @param stage  Stage of the test to get arguments for.
50        @return       Dictionary of arguments.
51
52        """
53        if stage == 0:
54            return {'perform_init': True,
55                    'pre_init_delay': self.pre_init_delay}
56        else:
57            return {'perform_init': False}
58
59    def run_stage(self, stage):
60        """Run the client-side test.
61
62        @param stage: Stage of the test to run.
63
64        """
65        full_stage = 'iteration.%s/%s' % (self.iteration, STAGE_NAME[stage])
66        logging.info('Run stage %s', full_stage)
67        self.client_at.run_test(test_name=self.client_test,
68                                results_dir=full_stage,
69                                check_client_result=True,
70                                **self.stage_args(stage))
71        client_keyval = os.path.join(self.outputdir, full_stage,
72                self.client_test, 'results', 'keyval')
73        self.client_results[stage] = utils.read_keyval(client_keyval,
74                type_tag='perf')
75
76    def save_perf_data(self):
77        """Extract perf data from client-side test results."""
78        for bmname, bm in BENCHMARKS.iteritems():
79            try:
80                self.perf_results[bmname].append(
81                        self.client_results[bm['stage']][bm['name']])
82            except:
83                logging.warning('Failed to extract %s from client results',
84                                bmname)
85                self.perf_results[bmname].append(None)
86                pass
87
88    def output_benchmark(self, bmname):
89        """Output a benchmark.
90
91        @param bmname: Name of the benchmark.
92
93        """
94        bm = BENCHMARKS[bmname]
95        values = self.perf_results[bmname]
96        if not bm.get('upload', True):
97            return
98        self.output_perf_value(
99                description=bmname,
100                value=[x for x in values if x is not None],
101                units=bm.get('units', 'seconds'),
102                higher_is_better=False,
103                graph=self.graph_name)
104
105    def display_perf_headers(self):
106        """Add headers for the results table to the info log."""
107        hdr = "# "
108        for bm in BENCHMARKS.itervalues():
109            hdr += bm['display'] + ' '
110        logging.info('# Results for delay = %.2f sec', self.pre_init_delay)
111        logging.info(hdr)
112
113    def display_perf_line(self, n):
114        """Add one iteration results line to the info log.
115
116        @param n: Number of the iteration.
117
118        """
119        line = "# "
120        for bmname in BENCHMARKS:
121            value = self.perf_results[bmname][n]
122            if value is None:
123                line += '    None '
124            else:
125                line += '%8.2f ' % value
126        logging.info(line)
127
128    def display_perf_stats(self, name, func):
129        """ Add results statistics line to the info log.
130
131        @param name: Name of the statistic.
132        @param func: Function to reduce the list of results.
133
134        """
135        line = "# "
136        for bmname in BENCHMARKS:
137            line += '%8.2f ' % func(self.perf_results[bmname])
138        logging.info('# %s:', name)
139        logging.info(line)
140
141    def process_perf_data(self):
142        """Process performance data from all iterations."""
143        logging.info('Process perf data')
144        logging.debug('Results: %s', self.perf_results)
145
146        if self.upload_perf:
147            for bmname in BENCHMARKS:
148                self.output_benchmark(bmname)
149
150        logging.info('##############################################')
151        self.display_perf_headers()
152        for iter in range(self.iteration - 1):
153            self.display_perf_line(iter)
154        self.display_perf_stats('Average', numpy.mean)
155        self.display_perf_stats('Min', min)
156        self.display_perf_stats('Max', max)
157        self.display_perf_stats('StdDev', lambda x: numpy.std(x, ddof=1))
158        logging.info('##############################################')
159
160    def run_once(self, host, pre_init_delay=0,
161                 upload_perf=False, graph_name=None):
162        """Run a single iteration.
163
164        @param pre_init_delay: Delay before initialization during first boot.
165        @param upload_perf: Do we need to upload the results?
166        @param graph_name: Graph name to use when uploading the results.
167
168        """
169        if self.iteration is None:
170            self.iteration = 1
171        logging.info('Start iteration %s', self.iteration)
172
173        self.client = host
174        self.pre_init_delay = pre_init_delay
175        self.upload_perf = upload_perf
176        self.graph_name = graph_name
177        self.client_results = {}
178        self.client_test = CLIENT_TEST_NAME
179        self.client_at = autotest.Autotest(self.client)
180
181        logging.info('Clear the owner before the test')
182        tpm_utils.ClearTPMOwnerRequest(self.client, wait_for_ready=False)
183
184        self.run_stage(STAGE_OOBE)
185        self.client.reboot()
186        self.run_stage(STAGE_REGULAR)
187        self.save_perf_data()
188
189    def postprocess(self):
190        """Run after all iterations in case of success."""
191        self.process_perf_data()
192
193    def cleanup(self):
194        """Run at the end regardless of success."""
195        logging.info('Cleanup')
196        tpm_utils.ClearTPMOwnerRequest(self.client, wait_for_ready=False)
197