1588dfe31SMichal Berger# SPDX-License-Identifier: BSD-3-Clause 2588dfe31SMichal Berger# Copyright (C) 2018 Intel Corporation. 3588dfe31SMichal Berger# All rights reserved. 4588dfe31SMichal Berger 53a359b79SKarol Lateckiimport os 63a359b79SKarol Lateckiimport re 73a359b79SKarol Lateckiimport json 83a359b79SKarol Lateckiimport logging 993e056baSKarol Lateckifrom subprocess import check_output 103a359b79SKarol Lateckifrom collections import OrderedDict 113a359b79SKarol Lateckifrom json.decoder import JSONDecodeError 12a42dfab1SKarol Latecki 13a42dfab1SKarol Latecki 143a359b79SKarol Lateckidef read_json_stats(file): 153a359b79SKarol Latecki with open(file, "r") as json_data: 163a359b79SKarol Latecki data = json.load(json_data) 17bd1b0714SKarol Latecki job_data = data["jobs"][0] # 0 because using aggregated results, fio group reporting 183a359b79SKarol Latecki 193a359b79SKarol Latecki # Check if latency is in nano or microseconds to choose correct dict key 203a359b79SKarol Latecki def get_lat_unit(key_prefix, dict_section): 213a359b79SKarol Latecki # key prefix - lat, clat or slat. 223a359b79SKarol Latecki # dict section - portion of json containing latency bucket in question 233a359b79SKarol Latecki # Return dict key to access the bucket and unit as string 243a359b79SKarol Latecki for k, _ in dict_section.items(): 253a359b79SKarol Latecki if k.startswith(key_prefix): 263a359b79SKarol Latecki return k, k.split("_")[1] 273a359b79SKarol Latecki 283a359b79SKarol Latecki def get_clat_percentiles(clat_dict_leaf): 293a359b79SKarol Latecki if "percentile" in clat_dict_leaf: 303a359b79SKarol Latecki p99_lat = float(clat_dict_leaf["percentile"]["99.000000"]) 313a359b79SKarol Latecki p99_9_lat = float(clat_dict_leaf["percentile"]["99.900000"]) 323a359b79SKarol Latecki p99_99_lat = float(clat_dict_leaf["percentile"]["99.990000"]) 333a359b79SKarol Latecki p99_999_lat = float(clat_dict_leaf["percentile"]["99.999000"]) 343a359b79SKarol Latecki 353a359b79SKarol Latecki return [p99_lat, p99_9_lat, p99_99_lat, p99_999_lat] 363a359b79SKarol Latecki else: 373a359b79SKarol Latecki # Latest fio versions do not provide "percentile" results if no 383a359b79SKarol Latecki # measurements were done, so just return zeroes 393a359b79SKarol Latecki return [0, 0, 0, 0] 403a359b79SKarol Latecki 41bd1b0714SKarol Latecki read_iops = float(job_data["read"]["iops"]) 42bd1b0714SKarol Latecki read_bw = float(job_data["read"]["bw"]) 43bd1b0714SKarol Latecki lat_key, lat_unit = get_lat_unit("lat", job_data["read"]) 44bd1b0714SKarol Latecki read_avg_lat = float(job_data["read"][lat_key]["mean"]) 45bd1b0714SKarol Latecki read_min_lat = float(job_data["read"][lat_key]["min"]) 46bd1b0714SKarol Latecki read_max_lat = float(job_data["read"][lat_key]["max"]) 47bd1b0714SKarol Latecki clat_key, clat_unit = get_lat_unit("clat", job_data["read"]) 483a359b79SKarol Latecki read_p99_lat, read_p99_9_lat, read_p99_99_lat, read_p99_999_lat = get_clat_percentiles( 49bd1b0714SKarol Latecki job_data["read"][clat_key]) 503a359b79SKarol Latecki 513a359b79SKarol Latecki if "ns" in lat_unit: 523a359b79SKarol Latecki read_avg_lat, read_min_lat, read_max_lat = [x / 1000 for x in [read_avg_lat, read_min_lat, read_max_lat]] 533a359b79SKarol Latecki if "ns" in clat_unit: 543a359b79SKarol Latecki read_p99_lat = read_p99_lat / 1000 553a359b79SKarol Latecki read_p99_9_lat = read_p99_9_lat / 1000 563a359b79SKarol Latecki read_p99_99_lat = read_p99_99_lat / 1000 573a359b79SKarol Latecki read_p99_999_lat = read_p99_999_lat / 1000 583a359b79SKarol Latecki 59bd1b0714SKarol Latecki write_iops = float(job_data["write"]["iops"]) 60bd1b0714SKarol Latecki write_bw = float(job_data["write"]["bw"]) 61bd1b0714SKarol Latecki lat_key, lat_unit = get_lat_unit("lat", job_data["write"]) 62bd1b0714SKarol Latecki write_avg_lat = float(job_data["write"][lat_key]["mean"]) 63bd1b0714SKarol Latecki write_min_lat = float(job_data["write"][lat_key]["min"]) 64bd1b0714SKarol Latecki write_max_lat = float(job_data["write"][lat_key]["max"]) 65bd1b0714SKarol Latecki clat_key, clat_unit = get_lat_unit("clat", job_data["write"]) 663a359b79SKarol Latecki write_p99_lat, write_p99_9_lat, write_p99_99_lat, write_p99_999_lat = get_clat_percentiles( 67bd1b0714SKarol Latecki job_data["write"][clat_key]) 683a359b79SKarol Latecki 693a359b79SKarol Latecki if "ns" in lat_unit: 703a359b79SKarol Latecki write_avg_lat, write_min_lat, write_max_lat = [x / 1000 for x in [write_avg_lat, write_min_lat, write_max_lat]] 713a359b79SKarol Latecki if "ns" in clat_unit: 723a359b79SKarol Latecki write_p99_lat = write_p99_lat / 1000 733a359b79SKarol Latecki write_p99_9_lat = write_p99_9_lat / 1000 743a359b79SKarol Latecki write_p99_99_lat = write_p99_99_lat / 1000 753a359b79SKarol Latecki write_p99_999_lat = write_p99_999_lat / 1000 763a359b79SKarol Latecki 773a359b79SKarol Latecki return [read_iops, read_bw, read_avg_lat, read_min_lat, read_max_lat, 783a359b79SKarol Latecki read_p99_lat, read_p99_9_lat, read_p99_99_lat, read_p99_999_lat, 793a359b79SKarol Latecki write_iops, write_bw, write_avg_lat, write_min_lat, write_max_lat, 803a359b79SKarol Latecki write_p99_lat, write_p99_9_lat, write_p99_99_lat, write_p99_999_lat] 813a359b79SKarol Latecki 823a359b79SKarol Latecki 83da55cb87SKarol Lateckidef read_target_stats(measurement_name, results_file_list, results_dir): 84da55cb87SKarol Latecki # Read additional metrics measurements done on target side and 85da55cb87SKarol Latecki # calculate the average from across all workload iterations. 86da55cb87SKarol Latecki # Currently only works for SAR CPU utilization and power draw measurements. 87da55cb87SKarol Latecki # Other (bwm-ng, pcm, dpdk memory) need to be refactored and provide more 88da55cb87SKarol Latecki # structured result files instead of a output dump. 89da55cb87SKarol Latecki total_util = 0 90da55cb87SKarol Latecki for result_file in results_file_list: 91da55cb87SKarol Latecki with open(os.path.join(results_dir, result_file), "r") as result_file_fh: 92da55cb87SKarol Latecki total_util += float(result_file_fh.read()) 93da55cb87SKarol Latecki avg_util = total_util / len(results_file_list) 94da55cb87SKarol Latecki 95da55cb87SKarol Latecki return {measurement_name: "{0:.3f}".format(avg_util)} 96da55cb87SKarol Latecki 97da55cb87SKarol Latecki 983a359b79SKarol Lateckidef parse_results(results_dir, csv_file): 993a359b79SKarol Latecki files = os.listdir(results_dir) 1003a359b79SKarol Latecki fio_files = filter(lambda x: ".fio" in x, files) 1013a359b79SKarol Latecki json_files = [x for x in files if ".json" in x] 102da55cb87SKarol Latecki sar_files = [x for x in files if "sar" in x and "util" in x] 103da55cb87SKarol Latecki pm_files = [x for x in files if "pm" in x and "avg" in x] 1043a359b79SKarol Latecki 1053a359b79SKarol Latecki headers = ["read_iops", "read_bw", "read_avg_lat_us", "read_min_lat_us", "read_max_lat_us", 1063a359b79SKarol Latecki "read_p99_lat_us", "read_p99.9_lat_us", "read_p99.99_lat_us", "read_p99.999_lat_us", 1073a359b79SKarol Latecki "write_iops", "write_bw", "write_avg_lat_us", "write_min_lat_us", "write_max_lat_us", 1083a359b79SKarol Latecki "write_p99_lat_us", "write_p99.9_lat_us", "write_p99.99_lat_us", "write_p99.999_lat_us"] 1093a359b79SKarol Latecki 1103a359b79SKarol Latecki header_line = ",".join(["Name", *headers]) 1113a359b79SKarol Latecki rows = set() 1123a359b79SKarol Latecki 1133a359b79SKarol Latecki for fio_config in fio_files: 1143a359b79SKarol Latecki logging.info("Getting FIO stats for %s" % fio_config) 1153a359b79SKarol Latecki job_name, _ = os.path.splitext(fio_config) 116da55cb87SKarol Latecki aggr_headers = ["iops", "bw", "avg_lat_us", "min_lat_us", "max_lat_us", 117da55cb87SKarol Latecki "p99_lat_us", "p99.9_lat_us", "p99.99_lat_us", "p99.999_lat_us"] 1183a359b79SKarol Latecki 1193a359b79SKarol Latecki # Look in the filename for rwmixread value. Function arguments do 1203a359b79SKarol Latecki # not have that information. 1213a359b79SKarol Latecki # TODO: Improve this function by directly using workload params instead 1223a359b79SKarol Latecki # of regexing through filenames. 1233a359b79SKarol Latecki if "read" in job_name: 1243a359b79SKarol Latecki rw_mixread = 1 1253a359b79SKarol Latecki elif "write" in job_name: 1263a359b79SKarol Latecki rw_mixread = 0 1273a359b79SKarol Latecki else: 1283a359b79SKarol Latecki rw_mixread = float(re.search(r"m_(\d+)", job_name).group(1)) / 100 1293a359b79SKarol Latecki 1303a359b79SKarol Latecki # If "_CPU" exists in name - ignore it 1313a359b79SKarol Latecki # Initiators for the same job could have different num_cores parameter 1323a359b79SKarol Latecki job_name = re.sub(r"_\d+CPU", "", job_name) 1333a359b79SKarol Latecki job_result_files = [x for x in json_files if x.startswith(job_name)] 134da55cb87SKarol Latecki sar_result_files = [x for x in sar_files if x.startswith(job_name)] 135bd1e8c2aSKamil Godzwon 136bd1e8c2aSKamil Godzwon # Collect all pm files for the current job 137bd1e8c2aSKamil Godzwon job_pm_files = [x for x in pm_files if x.startswith(job_name)] 138bd1e8c2aSKamil Godzwon 139bd1e8c2aSKamil Godzwon # Filter out data from DCMI sensors and socket/dram sensors 140bd1e8c2aSKamil Godzwon dcmi_sensors = [x for x in job_pm_files if "DCMI" in x] 141bd1e8c2aSKamil Godzwon socket_dram_sensors = [x for x in job_pm_files if "DCMI" not in x and ("socket" in x or "dram" in x)] 142bd1e8c2aSKamil Godzwon sdr_sensors = list(set(job_pm_files) - set(dcmi_sensors) - set(socket_dram_sensors)) 143bd1e8c2aSKamil Godzwon 144bd1e8c2aSKamil Godzwon # Determine the final list of pm_result_files, if DCMI file is present, use it as a primary source 145bd1e8c2aSKamil Godzwon # of power consumption data. If not, use SDR sensors data if available. If SDR sensors are not available, 146bd1e8c2aSKamil Godzwon # use socket and dram sensors as a fallback. 147bd1e8c2aSKamil Godzwon pm_result_files = dcmi_sensors or sdr_sensors 148bd1e8c2aSKamil Godzwon if not pm_result_files and socket_dram_sensors: 149bd1e8c2aSKamil Godzwon logging.warning("No DCMI or SDR data found for %s, using socket and dram data sensors as a fallback" % job_name) 150bd1e8c2aSKamil Godzwon pm_result_files = socket_dram_sensors 151da55cb87SKarol Latecki 152da55cb87SKarol Latecki logging.info("Matching result files for current fio config %s:" % job_name) 1533a359b79SKarol Latecki for j in job_result_files: 1543a359b79SKarol Latecki logging.info("\t %s" % j) 1553a359b79SKarol Latecki 1563a359b79SKarol Latecki # There may have been more than 1 initiator used in test, need to check that 1573a359b79SKarol Latecki # Result files are created so that string after last "_" separator is server name 1583a359b79SKarol Latecki inits_names = set([os.path.splitext(x)[0].split("_")[-1] for x in job_result_files]) 1593a359b79SKarol Latecki inits_avg_results = [] 1603a359b79SKarol Latecki for i in inits_names: 1613a359b79SKarol Latecki logging.info("\tGetting stats for initiator %s" % i) 1623a359b79SKarol Latecki # There may have been more than 1 test run for this job, calculate average results for initiator 1633a359b79SKarol Latecki i_results = [x for x in job_result_files if i in x] 1643a359b79SKarol Latecki i_results_filename = re.sub(r"run_\d+_", "", i_results[0].replace("json", "csv")) 1653a359b79SKarol Latecki 1663a359b79SKarol Latecki separate_stats = [] 1673a359b79SKarol Latecki for r in i_results: 1683a359b79SKarol Latecki try: 1693a359b79SKarol Latecki stats = read_json_stats(os.path.join(results_dir, r)) 1703a359b79SKarol Latecki separate_stats.append(stats) 171aea1abb9SKarol Latecki logging.info([float("{0:.3f}".format(x)) for x in stats]) 1723a359b79SKarol Latecki except JSONDecodeError: 1733a359b79SKarol Latecki logging.error("ERROR: Failed to parse %s results! Results might be incomplete!" % r) 1743a359b79SKarol Latecki 1753a359b79SKarol Latecki init_results = [sum(x) for x in zip(*separate_stats)] 1763a359b79SKarol Latecki init_results = [x / len(separate_stats) for x in init_results] 177aea1abb9SKarol Latecki init_results = [round(x, 3) for x in init_results] 1783a359b79SKarol Latecki inits_avg_results.append(init_results) 1793a359b79SKarol Latecki 1803a359b79SKarol Latecki logging.info("\tAverage results for initiator %s" % i) 1813a359b79SKarol Latecki logging.info(init_results) 1823a359b79SKarol Latecki with open(os.path.join(results_dir, i_results_filename), "w") as fh: 1833a359b79SKarol Latecki fh.write(header_line + "\n") 1843a359b79SKarol Latecki fh.write(",".join([job_name, *["{0:.3f}".format(x) for x in init_results]]) + "\n") 1853a359b79SKarol Latecki 1863a359b79SKarol Latecki # Sum results of all initiators running this FIO job. 187*34edd9f1SKamil Godzwon # Latency results are an average of latencies from across all initiators. 1883a359b79SKarol Latecki inits_avg_results = [sum(x) for x in zip(*inits_avg_results)] 1893a359b79SKarol Latecki inits_avg_results = OrderedDict(zip(headers, inits_avg_results)) 1903a359b79SKarol Latecki for key in inits_avg_results: 1913a359b79SKarol Latecki if "lat" in key: 1923a359b79SKarol Latecki inits_avg_results[key] /= len(inits_names) 1933a359b79SKarol Latecki 1943a359b79SKarol Latecki # Aggregate separate read/write values into common labels 1953a359b79SKarol Latecki # Take rw_mixread into consideration for mixed read/write workloads. 1963a359b79SKarol Latecki aggregate_results = OrderedDict() 1973a359b79SKarol Latecki for h in aggr_headers: 1983a359b79SKarol Latecki read_stat, write_stat = [float(value) for key, value in inits_avg_results.items() if h in key] 1993a359b79SKarol Latecki if "lat" in h: 2003a359b79SKarol Latecki _ = rw_mixread * read_stat + (1 - rw_mixread) * write_stat 2013a359b79SKarol Latecki else: 2023a359b79SKarol Latecki _ = read_stat + write_stat 2033a359b79SKarol Latecki aggregate_results[h] = "{0:.3f}".format(_) 2043a359b79SKarol Latecki 205da55cb87SKarol Latecki if sar_result_files: 206da55cb87SKarol Latecki aggr_headers.append("target_avg_cpu_util") 207da55cb87SKarol Latecki aggregate_results.update(read_target_stats("target_avg_cpu_util", sar_result_files, results_dir)) 208da55cb87SKarol Latecki 209da55cb87SKarol Latecki if pm_result_files: 210da55cb87SKarol Latecki aggr_headers.append("target_avg_power") 211da55cb87SKarol Latecki aggregate_results.update(read_target_stats("target_avg_power", pm_result_files, results_dir)) 212da55cb87SKarol Latecki 2133a359b79SKarol Latecki rows.add(",".join([job_name, *aggregate_results.values()])) 2143a359b79SKarol Latecki 215da55cb87SKarol Latecki # Create empty results file with just the header line 216da55cb87SKarol Latecki aggr_header_line = ",".join(["Name", *aggr_headers]) 217da55cb87SKarol Latecki with open(os.path.join(results_dir, csv_file), "w") as fh: 218da55cb87SKarol Latecki fh.write(aggr_header_line + "\n") 219da55cb87SKarol Latecki 2203a359b79SKarol Latecki # Save results to file 2213a359b79SKarol Latecki for row in rows: 2223a359b79SKarol Latecki with open(os.path.join(results_dir, csv_file), "a") as fh: 2233a359b79SKarol Latecki fh.write(row + "\n") 2243a359b79SKarol Latecki logging.info("You can find the test results in the file %s" % os.path.join(results_dir, csv_file)) 225