1# Copyright 2017 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 os 6import re 7import json 8import time 9import shutil 10import logging 11 12from autotest_lib.client.common_lib.cros import perf_stat_lib 13 14 15def get_cpu_usage(system_facade, measurement_duration_seconds): 16 """ 17 Returns cpu usage in %. 18 19 @param system_facade: A SystemFacadeRemoteAdapter to access 20 the CPU capture functionality from the DUT. 21 @param measurement_duration_seconds: CPU metric capture duration. 22 23 @returns current CPU usage percentage. 24 25 """ 26 cpu_usage_start = system_facade.get_cpu_usage() 27 time.sleep(measurement_duration_seconds) 28 cpu_usage_end = system_facade.get_cpu_usage() 29 return system_facade.compute_active_cpu_time( 30 cpu_usage_start, cpu_usage_end) * 100 31 32 33def get_memory_usage(system_facade): 34 """ 35 Returns total used memory in %. 36 37 @param system_facade: A SystemFacadeRemoteAdapter to access 38 the memory capture functionality from the DUT. 39 40 @returns current memory used. 41 42 """ 43 total_memory = system_facade.get_mem_total() 44 return ((total_memory - system_facade.get_mem_free()) 45 * 100 / total_memory) 46 47 48def get_temperature_data(client, system_facade): 49 """ 50 Returns temperature sensor data in Celcius. 51 52 @param system_facade: A SystemFacadeRemoteAdapter to access the temperature 53 capture functionality from the DUT. 54 55 @returns current CPU temperature. 56 57 """ 58 ectool = client.run('ectool version', ignore_status=True) 59 if not ectool.exit_status: 60 ec_temp = system_facade.get_ec_temperatures() 61 return ec_temp[1] 62 else: 63 temp_sensor_name = 'temp0' 64 if not temp_sensor_name: 65 return 0 66 MOSYS_OUTPUT_RE = re.compile('(\w+)="(.*?)"') 67 values = {} 68 cmd = 'mosys -k sensor print thermal %s' % temp_sensor_name 69 for kv in MOSYS_OUTPUT_RE.finditer(client.run_output(cmd)): 70 key, value = kv.groups() 71 if key == 'reading': 72 value = int(value) 73 values[key] = value 74 return values['reading'] 75 76 77#TODO(krishnargv): Replace _get_point_id with a call to the 78# _get_id_from_version method of the perf_uploader.py. 79def get_point_id(cros_version, epoch_minutes, version_pattern): 80 """ 81 Compute point ID from ChromeOS version number and epoch minutes. 82 83 @param cros_version: String of ChromeOS version number. 84 @param epoch_minutes: String of minutes since 1970. 85 86 @returns unique integer ID computed from given version and epoch. 87 88 """ 89 # Number of digits from each part of the Chrome OS version string. 90 cros_version_col_widths = [0, 4, 3, 2] 91 92 def get_digits(version_num, column_widths): 93 if re.match(version_pattern, version_num): 94 computed_string = '' 95 version_parts = version_num.split('.') 96 for i, version_part in enumerate(version_parts): 97 if column_widths[i]: 98 computed_string += version_part.zfill(column_widths[i]) 99 return computed_string 100 else: 101 return None 102 103 cros_digits = get_digits(cros_version, cros_version_col_widths) 104 epoch_digits = epoch_minutes[-8:] 105 if not cros_digits: 106 return None 107 return int(epoch_digits + cros_digits) 108 109 110def open_perf_file(file_path): 111 """ 112 Open a perf file. Write header line if new. Return file object. 113 114 If the file on |file_path| already exists, then open file for 115 appending only. Otherwise open for writing only. 116 117 @param file_path: file path for perf file. 118 119 @returns file object for the perf file. 120 121 """ 122 if os.path.isfile(file_path): 123 perf_file = open(file_path, 'a+') 124 else: 125 perf_file = open(file_path, 'w') 126 perf_file.write('Time,CPU,Memory,Temperature (C)\r\n') 127 return perf_file 128 129 130def modulo_time(timer, interval): 131 """ 132 Get time eplased on |timer| for the |interval| modulus. 133 134 Value returned is used to adjust the timer so that it is 135 synchronized with the current interval. 136 137 @param timer: time on timer, in seconds. 138 @param interval: period of time in seconds. 139 140 @returns time elapsed from the start of the current interval. 141 142 """ 143 return timer % int(interval) 144 145 146def syncup_time(timer, interval): 147 """ 148 Get time remaining on |timer| for the |interval| modulus. 149 150 Value returned is used to induce sleep just long enough to put the 151 process back in sync with the timer. 152 153 @param timer: time on timer, in seconds. 154 @param interval: period of time in seconds. 155 156 @returns time remaining till the end of the current interval. 157 158 """ 159 return interval - (timer % int(interval)) 160 161 162def append_to_aggregated_file(ts_file, ag_file): 163 """ 164 Append contents of perf timestamp file to perf aggregated file. 165 166 @param ts_file: file handle for performance timestamped file. 167 @param ag_file: file handle for performance aggregated file. 168 169 """ 170 next(ts_file) # Skip fist line (the header) of timestamped file. 171 for line in ts_file: 172 ag_file.write(line) 173 174 175def copy_aggregated_to_resultsdir(resultsdir, aggregated_fpath, f_name): 176 """Copy perf aggregated file to results dir for AutoTest results. 177 178 Note: The AutoTest results default directory is located at /usr/local/ 179 autotest/results/default/longevity_Tracker/results 180 181 @param resultsdir: Directory name where the perf results are stored. 182 @param aggregated_fpath: file path to Aggregated performance values. 183 @param f_name: Name of the perf File 184 """ 185 results_fpath = os.path.join(resultsdir, f_name) 186 shutil.copy(aggregated_fpath, results_fpath) 187 logging.info('Copied %s to %s)', aggregated_fpath, results_fpath) 188 189 190def record_90th_metrics(perf_values, perf_metrics): 191 """Record 90th percentile metric of attribute performance values. 192 193 @param perf_values: dict attribute performance values. 194 @param perf_metrics: dict attribute 90%-ile performance metrics. 195 """ 196 # Calculate 90th percentile for each attribute. 197 cpu_values = perf_values['cpu'] 198 mem_values = perf_values['mem'] 199 temp_values = perf_values['temp'] 200 cpu_metric = perf_stat_lib.get_kth_percentile(cpu_values, .90) 201 mem_metric = perf_stat_lib.get_kth_percentile(mem_values, .90) 202 temp_metric = perf_stat_lib.get_kth_percentile(temp_values, .90) 203 204 logging.info('Performance values: %s', perf_values) 205 logging.info('90th percentile: cpu: %s, mem: %s, temp: %s', 206 cpu_metric, mem_metric, temp_metric) 207 208 # Append 90th percentile to each attribute performance metric. 209 perf_metrics['cpu'].append(cpu_metric) 210 perf_metrics['mem'].append(mem_metric) 211 perf_metrics['temp'].append(temp_metric) 212 213 214def get_median_metrics(metrics): 215 """ 216 Returns median of each attribute performance metric. 217 218 If no metric values were recorded, return 0 for each metric. 219 220 @param metrics: dict of attribute performance metric lists. 221 222 @returns dict of attribute performance metric medians. 223 224 """ 225 if len(metrics['cpu']): 226 cpu_metric = perf_stat_lib.get_median(metrics['cpu']) 227 mem_metric = perf_stat_lib.get_median(metrics['mem']) 228 temp_metric = perf_stat_lib.get_median(metrics['temp']) 229 else: 230 cpu_metric = 0 231 mem_metric = 0 232 temp_metric = 0 233 logging.info('Median of 90th percentile: cpu: %s, mem: %s, temp: %s', 234 cpu_metric, mem_metric, temp_metric) 235 return {'cpu': cpu_metric, 'mem': mem_metric, 'temp': temp_metric} 236 237 238def read_perf_results(resultsdir, resultsfile): 239 """ 240 Read perf results from results-chart.json file for Perf Dashboard. 241 242 @returns dict of perf results, formatted as JSON chart data. 243 244 """ 245 results_file = os.path.join(resultsdir, resultsfile) 246 with open(results_file, 'r') as fp: 247 contents = fp.read() 248 chart_data = json.loads(contents) 249 # TODO(krishnargv): refactor this with a better method to delete. 250 open(results_file, 'w').close() 251 return chart_data 252 253 254def verify_perf_params(expected_params, perf_params): 255 """ 256 Verify that all the expected paramaters were passed to the test. 257 258 Return True if the perf_params dict passed via the control file 259 has all of the the expected parameters and have valid values. 260 261 @param expected_params: list of expected parameters 262 @param perf_params: dict of the paramaters passed via control file. 263 264 @returns True if the perf_params dict is valid, else returns False. 265 266 """ 267 for param in expected_params: 268 if param not in perf_params or not perf_params[param]: 269 return False 270 return True