普通文本  |  379行  |  15.54 KB

# Copyright 2017 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.

import os
import csv
import json
import time
import urllib
import urllib2
import logging
import httplib

import enterprise_longevity_helper
from autotest_lib.client.common_lib import error
from autotest_lib.client.common_lib.cros import tpm_utils
from autotest_lib.server import autotest
from autotest_lib.server import test
from autotest_lib.server.cros.multimedia import remote_facade_factory


STABILIZATION_DURATION = 60
MEASUREMENT_DURATION_SECONDS = 10
TMP_DIRECTORY = '/tmp/'
PERF_FILE_NAME_PREFIX = 'perf'
VERSION_PATTERN = r'^(\d+)\.(\d+)\.(\d+)$'
DASHBOARD_UPLOAD_URL = 'https://chromeperf.appspot.com/add_point'
EXPECTED_PARAMS = ['perf_capture_iterations',  'perf_capture_duration',
                   'sample_interval', 'metric_interval', 'test_type',
                   'kiosk_app_attributes']


class PerfUploadingError(Exception):
    """Exception raised in perf_uploader."""
    pass


class enterprise_LongevityTrackerServer(test.test):
    """
    Run Longevity Test: Collect performance data over long duration.

    Run enterprise_KioskEnrollment and clear the TPM as necessary. After
    enterprise enrollment is successful, collect and log cpu, memory, and
    temperature data from the device under test.

    """
    version = 1


    def initialize(self):
        self.temp_dir = os.path.split(self.tmpdir)[0]


    #TODO(krishnargv@): Add a method to retrieve the version of the
    #                   Kiosk app from its manifest.
    def _initialize_test_variables(self):
        """Initialize test variables that will be uploaded to the dashboard."""
        self.board_name = self.system_facade.get_current_board()
        self.chromeos_version = self.system_facade.get_chromeos_release_version()
        epoch_minutes = str(int(time.time() / 60))
        self.point_id = enterprise_longevity_helper.get_point_id(
                self.chromeos_version, epoch_minutes, VERSION_PATTERN)
        self.test_suite_name = self.tagged_testname
        self.perf_capture_duration = self.perf_params['perf_capture_duration']
        self.sample_interval = self.perf_params['sample_interval']
        self.metric_interval = self.perf_params['metric_interval']
        self.perf_results = {'cpu': '0', 'mem': '0', 'temp': '0'}


    def elapsed_time(self, mark_time):
        """
        Get time elapsed since |mark_time|.

        @param mark_time: point in time from which elapsed time is measured.

        @returns time elapsed since the marked time.

        """
        return time.time() - mark_time


    #TODO(krishnargv):  Replace _format_data_for_upload with a call to the
    #                   _format_for_upload method of the perf_uploader.py
    def _format_data_for_upload(self, chart_data):
        """
        Collect chart data into an uploadable data JSON object.

        @param chart_data: performance results formatted as chart data.

        """
        perf_values = {
            'format_version': '1.0',
            'benchmark_name': self.test_suite_name,
            'charts': chart_data,
        }
        #TODO(krishnargv): Add a method to capture the chrome_version.
        dash_entry = {
            'master': 'ChromeOS_Enterprise',
            'bot': 'cros-%s' % self.board_name,
            'point_id': self.point_id,
            'versions': {
                'cros_version': self.chromeos_version,

            },
            'supplemental': {
                'default_rev': 'r_cros_version',
                'kiosk_app_name': 'a_' + self.kiosk_app_name,

            },
            'chart_data': perf_values
        }
        return {'data': json.dumps(dash_entry)}


    #TODO(krishnargv):  Replace _send_to_dashboard with a call to the
    #                   _send_to_dashboard method of the perf_uploader.py
    def _send_to_dashboard(self, data_obj):
        """
        Send formatted perf data to the perf dashboard.

        @param data_obj: data object as returned by _format_data_for_upload().

        @raises PerfUploadingError if an exception was raised when uploading.

        """
        logging.debug('Data_obj to be uploaded: %s', data_obj)
        encoded = urllib.urlencode(data_obj)
        req = urllib2.Request(DASHBOARD_UPLOAD_URL, encoded)
        try:
            urllib2.urlopen(req)
        except urllib2.HTTPError as e:
            raise PerfUploadingError('HTTPError: %d %s for JSON %s\n' %
                                     (e.code, e.msg, data_obj['data']))
        except urllib2.URLError as e:
            raise PerfUploadingError('URLError: %s for JSON %s\n' %
                                     (str(e.reason), data_obj['data']))
        except httplib.HTTPException:
            raise PerfUploadingError('HTTPException for JSON %s\n' %
                                     data_obj['data'])


    def _write_perf_keyvals(self, perf_results):
        """
        Write perf results to keyval file for AutoTest results.

        @param perf_results: dict of attribute performance metrics.

        """
        perf_keyval = {}
        perf_keyval['cpu_usage'] = perf_results['cpu']
        perf_keyval['memory_usage'] = perf_results['mem']
        perf_keyval['temperature'] = perf_results['temp']
        self.write_perf_keyval(perf_keyval)


    def _write_perf_results(self, perf_results):
        """
        Write perf results to results-chart.json file for Perf Dashboard.

        @param perf_results: dict of attribute performance metrics.

        """
        cpu_metric = perf_results['cpu']
        mem_metric = perf_results['mem']
        ec_metric = perf_results['temp']
        self.output_perf_value(description='cpu_usage', value=cpu_metric,
                               units='percent', higher_is_better=False)
        self.output_perf_value(description='mem_usage', value=mem_metric,
                               units='percent', higher_is_better=False)
        self.output_perf_value(description='max_temp', value=ec_metric,
                               units='Celsius', higher_is_better=False)


    def _record_perf_measurements(self, perf_values, perf_writer):
        """
        Record attribute performance measurements, and write to file.

        @param perf_values: dict of attribute performance values.
        @param perf_writer: file to write performance measurements.

        """
        # Get performance measurements.
        cpu_usage = '%.3f' % enterprise_longevity_helper.get_cpu_usage(
                self.system_facade, MEASUREMENT_DURATION_SECONDS)
        mem_usage = '%.3f' % enterprise_longevity_helper.get_memory_usage(
                    self.system_facade)
        max_temp = '%.3f' % enterprise_longevity_helper.get_temperature_data(
                self.client, self.system_facade)

        # Append measurements to attribute lists in perf values dictionary.
        perf_values['cpu'].append(float(cpu_usage))
        perf_values['mem'].append(float(mem_usage))
        perf_values['temp'].append(float(max_temp))

        # Write performance measurements to perf timestamped file.
        time_stamp = time.strftime('%Y/%m/%d %H:%M:%S')
        perf_writer.writerow([time_stamp, cpu_usage, mem_usage, max_temp])
        logging.info('Time: %s, CPU: %r, Mem: %r, Temp: %r',
                     time_stamp, cpu_usage, mem_usage, max_temp)


    def _setup_kiosk_app_on_dut(self, kiosk_app_attributes=None):
        """Enroll the DUT and setup a Kiosk app."""
        info = self.client.host_info_store.get()
        app_config_id = info.get_label_value('app_config_id')
        if app_config_id and app_config_id.startswith(':'):
            app_config_id = app_config_id[1:]
        if kiosk_app_attributes:
            kiosk_app_attributes = kiosk_app_attributes.rstrip()
            self.kiosk_app_name, ext_id = kiosk_app_attributes.split(':')[:2]

        tpm_utils.ClearTPMOwnerRequest(self.client)
        logging.info("Enrolling the DUT to Kiosk mode")
        autotest.Autotest(self.client).run_test(
                'enterprise_KioskEnrollment',
                kiosk_app_attributes=kiosk_app_attributes,
                check_client_result=True)

        if self.kiosk_app_name == 'riseplayer':
            self.kiosk_facade.config_rise_player(ext_id, app_config_id)


    def _run_perf_capture_cycle(self):
        """
        Track performance of Chrome OS over a long period of time.

        This method collects performance measurements, and calculates metrics
        to upload to the performance dashboard. It creates two files to
        collect and store performance values and results: perf_<timestamp>.csv
        and perf_aggregated.csv.

        At the start, it creates a unique perf timestamped file in the test's
        temp_dir. As the cycle runs, it saves a time-stamped performance
        value after each sample interval. Periodically, it calculates
        the 90th percentile performance metrics from these values.

        The perf_<timestamp> files on the device will survive multiple runs
        of the longevity_Tracker by the server-side test, and will also
        survive multiple runs of the server-side test.

        At the end, it opens the perf aggregated file in the test's temp_dir,
        and appends the contents of the perf timestamped file. It then
        copies the perf aggregated file to the results directory as perf.csv.
        This perf.csv file will be consumed by the AutoTest backend when the
        server-side test ends.

        Note that the perf_aggregated.csv file will grow larger with each run
        of longevity_Tracker on the device by the server-side test.

        This method will capture perf metrics every SAMPLE_INTERVAL secs, at
        each METRIC_INTERVAL the 90 percentile of the collected metrics is
        calculated and saved. The perf capture runs for PERF_CAPTURE_DURATION
        secs. At the end of the PERF_CAPTURE_DURATION time interval the median
        value of all 90th percentile metrics is returned.

        @returns list of median performance metrics.

        """
        test_start_time = time.time()

        perf_values = {'cpu': [], 'mem': [], 'temp': []}
        perf_metrics = {'cpu': [], 'mem': [], 'temp': []}

         # Create perf_<timestamp> file and writer.
        timestamp_fname = (PERF_FILE_NAME_PREFIX +
                           time.strftime('_%Y-%m-%d_%H-%M') + '.csv')
        timestamp_fpath = os.path.join(self.temp_dir, timestamp_fname)
        timestamp_file = enterprise_longevity_helper.open_perf_file(
                timestamp_fpath)
        timestamp_writer = csv.writer(timestamp_file)

        # Align time of loop start with the sample interval.
        test_elapsed_time = self.elapsed_time(test_start_time)
        time.sleep(enterprise_longevity_helper.syncup_time(
                test_elapsed_time, self.sample_interval))
        test_elapsed_time = self.elapsed_time(test_start_time)

        metric_start_time = time.time()
        metric_prev_time = metric_start_time

        metric_elapsed_prev_time = self.elapsed_time(metric_prev_time)
        offset = enterprise_longevity_helper.modulo_time(
                metric_elapsed_prev_time, self.metric_interval)
        metric_timer = metric_elapsed_prev_time + offset

        while self.elapsed_time(test_start_time) <= self.perf_capture_duration:
            self._record_perf_measurements(perf_values, timestamp_writer)

            # Periodically calculate and record 90th percentile metrics.
            metric_elapsed_prev_time = self.elapsed_time(metric_prev_time)
            metric_timer = metric_elapsed_prev_time + offset
            if metric_timer >= self.metric_interval:
                enterprise_longevity_helper.record_90th_metrics(
                        perf_values, perf_metrics)
                perf_values = {'cpu': [], 'mem': [], 'temp': []}

            # Set previous time to current time.
                metric_prev_time = time.time()
                metric_elapsed_prev_time = self.elapsed_time(metric_prev_time)

                metric_elapsed_time = self.elapsed_time(metric_start_time)
                offset = enterprise_longevity_helper.modulo_time(
                    metric_elapsed_time, self.metric_interval)

                # Set the timer to time elapsed plus offset to next interval.
                metric_timer = metric_elapsed_prev_time + offset

            # Sync the loop time to the sample interval.
            test_elapsed_time = self.elapsed_time(test_start_time)
            time.sleep(enterprise_longevity_helper.syncup_time(
                    test_elapsed_time, self.sample_interval))

        # Close perf timestamp file.
        timestamp_file.close()

         # Open perf timestamp file to read, and aggregated file to append.
        timestamp_file = open(timestamp_fpath, 'r')
        aggregated_fname = (PERF_FILE_NAME_PREFIX + '_aggregated.csv')
        aggregated_fpath = os.path.join(self.temp_dir, aggregated_fname)
        aggregated_file = enterprise_longevity_helper.open_perf_file(
                aggregated_fpath)

         # Append contents of perf timestamp file to perf aggregated file.
        enterprise_longevity_helper.append_to_aggregated_file(
                timestamp_file, aggregated_file)
        timestamp_file.close()
        aggregated_file.close()

        # Copy perf aggregated file to test results directory.
        enterprise_longevity_helper.copy_aggregated_to_resultsdir(
                self.resultsdir, aggregated_fpath, 'perf.csv')

        # Return median of each attribute performance metric.
        logging.info("Perf_metrics: %r ", perf_metrics)
        return enterprise_longevity_helper.get_median_metrics(perf_metrics)


    def run_once(self, host=None, perf_params=None):
        self.client = host
        self.kiosk_app_name = None
        self.perf_params = perf_params
        logging.info('Perf params: %r', self.perf_params)

        if not enterprise_longevity_helper.verify_perf_params(
                EXPECTED_PARAMS, self.perf_params):
            raise error.TestFail('Missing or incorrect perf_params in the'
                                 ' control file. Refer to the README.txt for'
                                 ' info on perf params.: %r'
                                  %(self.perf_params))

        factory = remote_facade_factory.RemoteFacadeFactory(
                host, no_chrome=True)
        self.system_facade = factory.create_system_facade()
        self.kiosk_facade = factory.create_kiosk_facade()

        self._setup_kiosk_app_on_dut(self.perf_params['kiosk_app_attributes'])
        time.sleep(STABILIZATION_DURATION)

        self._initialize_test_variables()

        for iteration in range(self.perf_params['perf_capture_iterations']):
            #TODO(krishnargv@): Add a method to verify that the Kiosk app is
            #                   active and is running on the DUT.
            logging.info("Running perf_capture Iteration: %d", iteration+1)
            self.perf_results = self._run_perf_capture_cycle()
            self._write_perf_keyvals(self.perf_results)
            self._write_perf_results(self.perf_results)

            # Post perf results directly to performance dashboard. You may view
            # uploaded data at https://chromeperf.appspot.com/new_points,
            # with test path pattern=ChromeOS_Enterprise/cros-*/longevity*/*
            if perf_params['test_type'] == 'multiple_samples':
                chart_data = enterprise_longevity_helper.read_perf_results(
                        self.resultsdir, 'results-chart.json')
                data_obj = self._format_data_for_upload(chart_data)
                self._send_to_dashboard(data_obj)

        tpm_utils.ClearTPMOwnerRequest(self.client)