# # Copyright(c) 2019-2021 Intel Corporation # SPDX-License-Identifier: BSD-3-Clause-Clear # import csv import json import re from datetime import timedelta from typing import List from api.cas import casadm from api.cas.cache_config import * from api.cas.casadm_params import * from api.cas.version import CasVersion from storage_devices.device import Device from test_utils.size import parse_unit from test_utils.output import CmdException class Stats(dict): def __str__(self): return json.dumps(self, default=lambda o: str(o), indent=2) def parse_stats_unit(unit: str): if unit is None: return "" unit = re.search(r".*[^\]]", unit).group() if unit == "s": return "s" elif unit == "%": return "%" elif unit == "Requests": return "requests" else: return parse_unit(unit) def get_filter(filter: List[StatsFilter]): """Prepare list of statistic sections which should be retrieved and parsed. """ if filter is None or StatsFilter.all in filter: _filter = [ f for f in StatsFilter if (f != StatsFilter.all and f != StatsFilter.conf) ] else: _filter = [ f for f in filter if (f != StatsFilter.all and f != StatsFilter.conf) ] return _filter def get_statistics( cache_id: int, core_id: int = None, io_class_id: int = None, filter: List[StatsFilter] = None, percentage_val: bool = False, ): stats = Stats() _filter = get_filter(filter) per_io_class = True if io_class_id is not None else False # No need to retrieve all stats if user specified only 'conf' flag if filter != [StatsFilter.conf]: csv_stats = casadm.print_statistics( cache_id=cache_id, core_id=core_id, per_io_class=per_io_class, io_class_id=io_class_id, filter=_filter, output_format=casadm.OutputFormat.csv, ).stdout.splitlines() if filter is None or StatsFilter.conf in filter or StatsFilter.all in filter: # Conf statistics have different unit or may have no unit at all. For parsing # convenience they are gathered separately. As this is only configuration stats # there is no risk they are divergent. conf_stats = casadm.print_statistics( cache_id=cache_id, core_id=core_id, per_io_class=per_io_class, io_class_id=io_class_id, filter=[StatsFilter.conf], output_format=casadm.OutputFormat.csv, ).stdout.splitlines() stat_keys = conf_stats[0] stat_values = conf_stats[1] for (name, val) in zip(stat_keys.split(","), stat_values.split(",")): # Some of configuration stats have no unit try: stat_name, stat_unit = name.split(" [") except ValueError: stat_name = name stat_unit = None stat_name = stat_name.lower() # 'dirty for' and 'cache size' stats occurs twice if stat_name in stats: continue stat_unit = parse_stats_unit(stat_unit) if isinstance(stat_unit, Unit): stats[stat_name] = Size(float(val), stat_unit) elif stat_unit == "s": stats[stat_name] = timedelta(seconds=int(val)) elif stat_unit == "": # Some of stats without unit can be a number like IDs, # some of them can be string like device path try: stats[stat_name] = float(val) except ValueError: stats[stat_name] = val # No need to parse all stats if user specified only 'conf' flag if filter == [StatsFilter.conf]: return stats stat_keys = csv_stats[0] stat_values = csv_stats[1] for (name, val) in zip(stat_keys.split(","), stat_values.split(",")): if percentage_val and " [%]" in name: stats[name.split(" [")[0].lower()] = float(val) elif not percentage_val and "[%]" not in name: stat_name, stat_unit = name.split(" [") stat_unit = parse_stats_unit(stat_unit) stat_name = stat_name.lower() if isinstance(stat_unit, Unit): stats[stat_name] = Size(float(val), stat_unit) elif stat_unit == "requests": stats[stat_name] = float(val) else: raise ValueError(f"Invalid unit {stat_unit}") return stats def get_caches(): # This method does not return inactive or detached CAS devices from api.cas.cache import Cache caches_list = [] lines = casadm.list_caches(OutputFormat.csv).stdout.split('\n') for line in lines: args = line.split(',') if args[0] == "cache": current_cache = Cache(Device(args[2])) caches_list.append(current_cache) return caches_list def get_cores(cache_id: int): from api.cas.core import Core, CoreStatus cores_list = [] lines = casadm.list_caches(OutputFormat.csv).stdout.split('\n') is_proper_core_line = False for line in lines: args = line.split(',') if args[0] == "core" and is_proper_core_line: core_status_str = args[3].lower() is_valid_status = CoreStatus[core_status_str].value[0] <= 1 if is_valid_status: cores_list.append(Core(args[2], cache_id)) if args[0] == "cache": is_proper_core_line = True if int(args[1]) == cache_id else False return cores_list def get_cas_devices_dict(): device_list = list(csv.DictReader(casadm.list_caches(OutputFormat.csv).stdout.split('\n'))) devices = {"core_pool": [], "caches": {}, "cores": {}} core_pool = False prev_cache_id = -1 for device in device_list: if device["type"] == "core pool": core_pool = True continue if device["type"] == "cache": core_pool = False prev_cache_id = int(device["id"]) devices["caches"].update( { int(device["id"]): { "device": device["disk"], "status": device["status"], } } ) elif device["type"] == "core": core = {"device": device["disk"], "status": device["status"]} if core_pool: devices["core_pool"].append(core) else: core.update({"cache_id": prev_cache_id}) devices["cores"].update( {(prev_cache_id, int(device["id"])): core} ) return devices def get_flushing_progress(cache_id: int, core_id: int = None): casadm_output = casadm.list_caches(OutputFormat.csv).stdout.splitlines() for line in casadm_output: line_elements = line.split(',') if core_id is not None and line_elements[0] == "core" \ and int(line_elements[1]) == core_id \ or core_id is None and line_elements[0] == "cache" \ and int(line_elements[1]) == cache_id: try: flush_line_elements = line_elements[3].split() flush_percent = flush_line_elements[1][1:] return float(flush_percent) except Exception: break raise CmdException(f"There is no flushing progress in casadm list output. (cache {cache_id}" f"{' core ' + str(core_id) if core_id is not None else ''})", casadm_output) def get_flush_parameters_alru(cache_id: int): casadm_output = casadm.get_param_cleaning_alru(cache_id, casadm.OutputFormat.csv).stdout.splitlines() flush_parameters = FlushParametersAlru() for line in casadm_output: if 'max buffers' in line: flush_parameters.flush_max_buffers = int(line.split(',')[1]) if 'Activity threshold' in line: flush_parameters.activity_threshold = Time(milliseconds=int(line.split(',')[1])) if 'Stale buffer time' in line: flush_parameters.staleness_time = Time(seconds=int(line.split(',')[1])) if 'Wake up time' in line: flush_parameters.wake_up_time = Time(seconds=int(line.split(',')[1])) return flush_parameters def get_flush_parameters_acp(cache_id: int): casadm_output = casadm.get_param_cleaning_acp(cache_id, casadm.OutputFormat.csv).stdout.splitlines() flush_parameters = FlushParametersAcp() for line in casadm_output: if 'max buffers' in line: flush_parameters.flush_max_buffers = int(line.split(',')[1]) if 'Wake up time' in line: flush_parameters.wake_up_time = Time(milliseconds=int(line.split(',')[1])) return flush_parameters def get_seq_cut_off_parameters(cache_id: int, core_id: int): casadm_output = casadm.get_param_cutoff( cache_id, core_id, casadm.OutputFormat.csv).stdout.splitlines() seq_cut_off_params = SeqCutOffParameters() for line in casadm_output: if 'Sequential cutoff threshold' in line: seq_cut_off_params.threshold = Size(int(line.split(',')[1]), Unit.KibiByte) if 'Sequential cutoff policy' in line: seq_cut_off_params.policy = SeqCutOffPolicy.from_name(line.split(',')[1]) if 'Sequential cutoff promotion request count threshold' in line: seq_cut_off_params.promotion_count = int(line.split(',')[1]) return seq_cut_off_params def get_casadm_version(): casadm_output = casadm.print_version(OutputFormat.csv).stdout.split('\n') version_str = casadm_output[1].split(',')[-1] return CasVersion.from_version_string(version_str) def get_io_class_list(cache_id: int): ret = [] casadm_output = casadm.list_io_classes(cache_id, OutputFormat.csv).stdout.splitlines() casadm_output.pop(0) # Remove header for line in casadm_output: values = line.split(",") ioclass = { "id": int(values[0]), "rule": values[1], "eviction_priority": int(values[2]), "allocation": float(values[3]), } ret.append(ioclass) return ret