def test_ioclass_stats_basic(random_cls): """ title: Basic test for retrieving IO class statistics. description: | Check if statistics are retrieved only for configured IO classes. pass_criteria: - Statistics are retrieved for configured IO classes. - Error is displayed when retrieving statistics for non-configured IO class. - Error is displayed when retrieving statistics for out of range IO class id. """ min_ioclass_id = 11 max_ioclass_id = 21 with TestRun.step("Test prepare"): prepare(random_cls) with TestRun.step("Prepare IO class config file"): ioclass_list = [] for class_id in range(min_ioclass_id, max_ioclass_id): ioclass_list.append(IoClass( class_id=class_id, rule=f"file_size:le:{4096 * class_id}&done", priority=22 )) IoClass.save_list_to_config_file(ioclass_list, True) with TestRun.step("Load IO class config file"): casadm.load_io_classes(cache_id, file=ioclass_config.default_config_file_path) with TestRun.step("Try retrieving IO class stats for all allowed id values " "and one out of range id"): for class_id in range(ioclass_config.MAX_IO_CLASS_ID + 2): out_of_range = " out of range" if class_id > ioclass_config.MAX_IO_CLASS_ID else "" with TestRun.group(f"Checking{out_of_range} IO class id {class_id}..."): expected = class_id == 0 or class_id in range(min_ioclass_id, max_ioclass_id) try: casadm.print_statistics( cache_id=cache_id, io_class_id=class_id, per_io_class=True) if not expected: TestRun.LOGGER.error( f"Stats retrieved for not configured IO class {class_id}") except CmdException as e: if expected: TestRun.LOGGER.error(f"Stats not retrieved for IO class id: {class_id}") elif class_id <= ioclass_config.MAX_IO_CLASS_ID: if not check_stderr_msg(e.output, get_stats_ioclass_id_not_configured): TestRun.LOGGER.error( f"Wrong message for unused IO class id: {class_id}") elif not check_stderr_msg(e.output, get_stats_ioclass_id_out_of_range): TestRun.LOGGER.error( f"Wrong message for out of range IO class id: {class_id}")
def get_stats_from_csv(cache_id: int, core_id: int = None): """ 'casadm -P' csv output has two lines: 1st - statistics names with units 2nd - statistics values This function returns dictionary with statistics names with units as keys and statistics values as values. """ output = print_statistics(cache_id, core_id, output_format=OutputFormat.csv) output = output.stdout.splitlines() keys = output[0].split(",") values = output[1].split(",") # return the keys and the values as a dictionary return dict(zip(keys, values))
def get_stats_from_table(cache_id: int, core_id: int = None): """ 'casadm -P' table output has a few sections: 1st - config section with two columns remaining - table sections with four columns This function returns dictionary with statistics names with units as keys and statistics values as values. """ output = print_statistics(cache_id, core_id, output_format=OutputFormat.table) output = output.stdout.splitlines() output_parts = [] # split 'casadm -P' output to sections and remove blank lines j = 0 for i, line in enumerate(output): if line == "" or i == len(output) - 1: output_parts.append(output[j:i]) j = i + 1 # the first part is config section conf_section = output_parts.pop(0) keys, values = (parse_core_conf_section(conf_section) if core_id else parse_cache_conf_section(conf_section)) # parse each remaining section for section in output_parts: # the remaining parts are table sections part_of_keys, part_of_values = parse_tables_section(section) # receive keys and values lists from every section keys.extend(part_of_keys) values.extend(part_of_values) # return the keys and the values as a dictionary return dict(zip(keys, values))
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