# 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 re import json import time import shutil import logging from autotest_lib.client.common_lib.cros import perf_stat_lib def get_cpu_usage(system_facade, measurement_duration_seconds): """ Returns cpu usage in %. @param system_facade: A SystemFacadeRemoteAdapter to access the CPU capture functionality from the DUT. @param measurement_duration_seconds: CPU metric capture duration. @returns current CPU usage percentage. """ cpu_usage_start = system_facade.get_cpu_usage() time.sleep(measurement_duration_seconds) cpu_usage_end = system_facade.get_cpu_usage() return system_facade.compute_active_cpu_time( cpu_usage_start, cpu_usage_end) * 100 def get_memory_usage(system_facade): """ Returns total used memory in %. @param system_facade: A SystemFacadeRemoteAdapter to access the memory capture functionality from the DUT. @returns current memory used. """ total_memory = system_facade.get_mem_total() return ((total_memory - system_facade.get_mem_free()) * 100 / total_memory) def get_temperature_data(client, system_facade): """ Returns temperature sensor data in Celcius. @param system_facade: A SystemFacadeRemoteAdapter to access the temperature capture functionality from the DUT. @returns current CPU temperature. """ ectool = client.run('ectool version', ignore_status=True) if not ectool.exit_status: ec_temp = system_facade.get_ec_temperatures() return ec_temp[1] else: temp_sensor_name = 'temp0' if not temp_sensor_name: return 0 MOSYS_OUTPUT_RE = re.compile('(\w+)="(.*?)"') values = {} cmd = 'mosys -k sensor print thermal %s' % temp_sensor_name for kv in MOSYS_OUTPUT_RE.finditer(client.run_output(cmd)): key, value = kv.groups() if key == 'reading': value = int(value) values[key] = value return values['reading'] #TODO(krishnargv): Replace _get_point_id with a call to the # _get_id_from_version method of the perf_uploader.py. def get_point_id(cros_version, epoch_minutes, version_pattern): """ Compute point ID from ChromeOS version number and epoch minutes. @param cros_version: String of ChromeOS version number. @param epoch_minutes: String of minutes since 1970. @returns unique integer ID computed from given version and epoch. """ # Number of digits from each part of the Chrome OS version string. cros_version_col_widths = [0, 4, 3, 2] def get_digits(version_num, column_widths): if re.match(version_pattern, version_num): computed_string = '' version_parts = version_num.split('.') for i, version_part in enumerate(version_parts): if column_widths[i]: computed_string += version_part.zfill(column_widths[i]) return computed_string else: return None cros_digits = get_digits(cros_version, cros_version_col_widths) epoch_digits = epoch_minutes[-8:] if not cros_digits: return None return int(epoch_digits + cros_digits) def open_perf_file(file_path): """ Open a perf file. Write header line if new. Return file object. If the file on |file_path| already exists, then open file for appending only. Otherwise open for writing only. @param file_path: file path for perf file. @returns file object for the perf file. """ if os.path.isfile(file_path): perf_file = open(file_path, 'a+') else: perf_file = open(file_path, 'w') perf_file.write('Time,CPU,Memory,Temperature (C)\r\n') return perf_file def modulo_time(timer, interval): """ Get time eplased on |timer| for the |interval| modulus. Value returned is used to adjust the timer so that it is synchronized with the current interval. @param timer: time on timer, in seconds. @param interval: period of time in seconds. @returns time elapsed from the start of the current interval. """ return timer % int(interval) def syncup_time(timer, interval): """ Get time remaining on |timer| for the |interval| modulus. Value returned is used to induce sleep just long enough to put the process back in sync with the timer. @param timer: time on timer, in seconds. @param interval: period of time in seconds. @returns time remaining till the end of the current interval. """ return interval - (timer % int(interval)) def append_to_aggregated_file(ts_file, ag_file): """ Append contents of perf timestamp file to perf aggregated file. @param ts_file: file handle for performance timestamped file. @param ag_file: file handle for performance aggregated file. """ next(ts_file) # Skip fist line (the header) of timestamped file. for line in ts_file: ag_file.write(line) def copy_aggregated_to_resultsdir(resultsdir, aggregated_fpath, f_name): """Copy perf aggregated file to results dir for AutoTest results. Note: The AutoTest results default directory is located at /usr/local/ autotest/results/default/longevity_Tracker/results @param resultsdir: Directory name where the perf results are stored. @param aggregated_fpath: file path to Aggregated performance values. @param f_name: Name of the perf File """ results_fpath = os.path.join(resultsdir, f_name) shutil.copy(aggregated_fpath, results_fpath) logging.info('Copied %s to %s)', aggregated_fpath, results_fpath) def record_90th_metrics(perf_values, perf_metrics): """Record 90th percentile metric of attribute performance values. @param perf_values: dict attribute performance values. @param perf_metrics: dict attribute 90%-ile performance metrics. """ # Calculate 90th percentile for each attribute. cpu_values = perf_values['cpu'] mem_values = perf_values['mem'] temp_values = perf_values['temp'] cpu_metric = perf_stat_lib.get_kth_percentile(cpu_values, .90) mem_metric = perf_stat_lib.get_kth_percentile(mem_values, .90) temp_metric = perf_stat_lib.get_kth_percentile(temp_values, .90) logging.info('Performance values: %s', perf_values) logging.info('90th percentile: cpu: %s, mem: %s, temp: %s', cpu_metric, mem_metric, temp_metric) # Append 90th percentile to each attribute performance metric. perf_metrics['cpu'].append(cpu_metric) perf_metrics['mem'].append(mem_metric) perf_metrics['temp'].append(temp_metric) def get_median_metrics(metrics): """ Returns median of each attribute performance metric. If no metric values were recorded, return 0 for each metric. @param metrics: dict of attribute performance metric lists. @returns dict of attribute performance metric medians. """ if len(metrics['cpu']): cpu_metric = perf_stat_lib.get_median(metrics['cpu']) mem_metric = perf_stat_lib.get_median(metrics['mem']) temp_metric = perf_stat_lib.get_median(metrics['temp']) else: cpu_metric = 0 mem_metric = 0 temp_metric = 0 logging.info('Median of 90th percentile: cpu: %s, mem: %s, temp: %s', cpu_metric, mem_metric, temp_metric) return {'cpu': cpu_metric, 'mem': mem_metric, 'temp': temp_metric} def read_perf_results(resultsdir, resultsfile): """ Read perf results from results-chart.json file for Perf Dashboard. @returns dict of perf results, formatted as JSON chart data. """ results_file = os.path.join(resultsdir, resultsfile) with open(results_file, 'r') as fp: contents = fp.read() chart_data = json.loads(contents) # TODO(krishnargv): refactor this with a better method to delete. open(results_file, 'w').close() return chart_data def verify_perf_params(expected_params, perf_params): """ Verify that all the expected paramaters were passed to the test. Return True if the perf_params dict passed via the control file has all of the the expected parameters and have valid values. @param expected_params: list of expected parameters @param perf_params: dict of the paramaters passed via control file. @returns True if the perf_params dict is valid, else returns False. """ for param in expected_params: if param not in perf_params or not perf_params[param]: return False return True