/* * Copyright(c) 2012-2019 Intel Corporation * SPDX-License-Identifier: BSD-3-Clause-Clear */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "cas_lib.h" #include "extended_err_msg.h" #include "cas_lib_utils.h" #include #include #include #include "csvparse.h" #include "statistics_view.h" #include "safeclib/safe_str_lib.h" #include "ocf/ocf_cache.h" #define IOCLASS_UNCLASSIFIED (0) #define UNIT_REQUESTS "Requests" #define UNIT_BLOCKS "4KiB blocks" #define ALLOWED_NUMBER_OF_ATTEMPTS 10 static inline float percentage(uint64_t numerator, uint64_t denominator) { float result; if (denominator) { result = 100.0 * numerator / denominator; } else { result = 0; } return result; } static inline long unsigned int cache_line_in_4k(uint64_t size, ocf_cache_line_size_t cache_line_size) { long unsigned int result; result = size * (cache_line_size / 4); return result; } static inline unsigned long bytes_to_4k(uint64_t size) { return (size + 4095UL) >> 12; } static float calc_gb(uint32_t clines) { return (float) clines * 4 * KiB / GiB; } static void print_dirty_for_time(uint32_t t, FILE *outfile) { uint32_t d, h, m, s; fprintf(outfile, "%u,[s],", t); if (!t) { fprintf(outfile, "Cache clean"); return; } d = t / (24 * 3600); h = (t % (24 * 3600)) / 3600; m = (t % 3600) / 60; s = (t % 60); if (d) { fprintf(outfile, "%u [d] ", d); } if (h) { fprintf(outfile, "%u [h] ", h); } if (m) { fprintf(outfile, "%u [m] ", m); } if (s) { fprintf(outfile, "%u [s] ", s); } } __attribute__((format(printf, 3, 4))) static void print_kv_pair(FILE *outfile, const char *title, const char *fmt, ...) { va_list ap; fprintf(outfile, TAG(KV_PAIR) "\"%s\",", title); va_start(ap, fmt); vfprintf(outfile, fmt, ap); va_end(ap); fprintf(outfile, "\n"); } static void print_kv_pair_time(FILE *outfile, const char *title, uint32_t time) { fprintf(outfile, TAG(KV_PAIR) "\"%s\",", title); print_dirty_for_time(time, outfile); fprintf(outfile, "\n"); } static void begin_record(FILE *outfile) { fprintf(outfile, TAG(RECORD) "\n"); } static void print_table_header(FILE *outfile, uint32_t ncols, ...) { va_list ap; const char *s; fprintf(outfile, TAG(TABLE_HEADER)); va_start(ap, ncols); while (ncols--) { s = va_arg(ap, const char *); fprintf(outfile, "\"%s\"%s", s, ncols ? "," : "\n"); } va_end(ap); } static void print_val_perc_table_elem(FILE *outfile, const char *tag, const char *title, const char *unit, float percent, const char * fmt, va_list ap) { fprintf(outfile, "%s\"%s\",", tag, title); vfprintf(outfile, fmt, ap); fprintf(outfile, ",%.1f", percent); if (unit) { fprintf(outfile, ",\"[%s]\"", unit); } fprintf(outfile, "\n"); } __attribute__((format(printf, 5, 6))) static inline void print_val_perc_table_row(FILE *outfile, const char *title, const char *unit, float percent, const char *fmt, ...) { va_list ap; va_start(ap, fmt); print_val_perc_table_elem(outfile, TAG(TABLE_ROW), title, unit, percent, fmt, ap); va_end(ap); } __attribute__((format(printf, 5, 6))) static inline void print_val_perc_table_section(FILE *outfile, const char *title, const char *unit, float percent, const char *fmt, ...) { va_list ap; va_start(ap, fmt); print_val_perc_table_elem(outfile, TAG(TABLE_SECTION), title, unit, percent, fmt, ap); va_end(ap); } static inline const char *make_row_title(const char *s1, const char *s2) { static char buffer[64]; snprintf(buffer, sizeof(buffer), "%s %s", s1, s2); return buffer; } static void print_core_conf(const struct kcas_core_info *info, uint32_t cache_size, FILE *outfile, ocf_cache_line_size_t cache_line_size) { uint64_t core_size; float core_size_gb; core_size = info->stats.core_size_bytes / KiB / 4; core_size_gb = calc_gb(core_size); print_kv_pair(outfile, "Core Id", "%i", info->core_id); print_kv_pair(outfile, "Core Device", "%s", info->core_path_name); print_kv_pair(outfile, "Exported Object", "/dev/cas%d-%d", info->cache_id, info->core_id); print_kv_pair(outfile, "Core Size", "%lu, [4KiB Blocks], %.2f, [GiB]", core_size, core_size_gb); print_kv_pair_time(outfile, "Dirty for", info->stats.dirty_for); print_kv_pair(outfile, "Status", "%s", get_core_state_name(info->state)); print_kv_pair(outfile, "Seq cutoff threshold", "%llu, [KiB]", info->stats.seq_cutoff_threshold / KiB); print_kv_pair(outfile, "Seq cutoff policy", "%s", seq_cutoff_policy_to_name(info->stats.seq_cutoff_policy)); } static void print_usage_header(FILE* outfile) { print_table_header(outfile, 4, "Usage statistics", "Count", "%", "[Units]"); } static void print_core_usage(const struct ocf_stats_core* exp_obj_stats, uint32_t cache_size, uint32_t cache_occupancy, FILE* outfile, ocf_cache_line_size_t line_size) { print_usage_header(outfile); print_val_perc_table_row(outfile, "Occupancy", UNIT_BLOCKS, percentage(exp_obj_stats->cache_occupancy, cache_size), "%lu", cache_line_in_4k(exp_obj_stats->cache_occupancy, line_size)); print_val_perc_table_row(outfile, "Free", UNIT_BLOCKS, percentage(cache_size - cache_occupancy, cache_size), "%lu", cache_line_in_4k(cache_size - cache_occupancy, line_size)); print_val_perc_table_row(outfile, "Clean", UNIT_BLOCKS, percentage(exp_obj_stats->cache_occupancy - exp_obj_stats->dirty, exp_obj_stats->cache_occupancy), "%lu", cache_line_in_4k(exp_obj_stats->cache_occupancy - exp_obj_stats->dirty, line_size)); print_val_perc_table_row(outfile, "Dirty", UNIT_BLOCKS, percentage(exp_obj_stats->dirty, exp_obj_stats->cache_occupancy), "%lu", cache_line_in_4k(exp_obj_stats->dirty, line_size)); } static void print_req_section(const struct ocf_stats_req *stats, const char *op_name, FILE *outfile, uint64_t total_reqs) { uint64_t cache_hits; float percent; cache_hits = stats->total - (stats->full_miss + stats->partial_miss); percent = percentage(cache_hits, total_reqs); print_val_perc_table_section(outfile, make_row_title(op_name, "hits"), UNIT_REQUESTS, percent, "%lu", cache_hits); percent = percentage(stats->partial_miss, total_reqs); print_val_perc_table_row(outfile, make_row_title(op_name, "partial misses"), UNIT_REQUESTS, percent, "%lu", stats->partial_miss); percent = percentage(stats->full_miss, total_reqs); print_val_perc_table_row(outfile, make_row_title(op_name, "full misses"), UNIT_REQUESTS, percent, "%lu", stats->full_miss); percent = percentage(stats->total, total_reqs); print_val_perc_table_row(outfile, make_row_title(op_name, "total"), UNIT_REQUESTS, percent, "%lu", stats->total); } static void print_req_stats(const struct ocf_stats_core *exp_obj_stats, FILE *outfile) { const struct ocf_stats_req *req_stats; float percent; uint64_t total_reqs = 0, serv_reqs = 0; print_table_header(outfile, 4, "Request statistics", "Count", "%", "[Units]"); total_reqs += exp_obj_stats->read_reqs.total; total_reqs += exp_obj_stats->write_reqs.total; serv_reqs = total_reqs; total_reqs += exp_obj_stats->read_reqs.pass_through; total_reqs += exp_obj_stats->write_reqs.pass_through; /* Section for reads. */ req_stats = &exp_obj_stats->read_reqs; print_req_section(req_stats, "Read", outfile, total_reqs); /* Section for writes. */ req_stats = &exp_obj_stats->write_reqs; print_req_section(req_stats, "Write", outfile, total_reqs); /* Pass-Through requests. */ percent = percentage(exp_obj_stats->read_reqs.pass_through, total_reqs); print_val_perc_table_section(outfile, "Pass-Through reads", UNIT_REQUESTS, percent, "%lu", exp_obj_stats->read_reqs.pass_through); percent = percentage(exp_obj_stats->write_reqs.pass_through, total_reqs); print_val_perc_table_row(outfile, "Pass-Through writes", UNIT_REQUESTS, percent, "%lu", exp_obj_stats->write_reqs.pass_through); /* Summary. */ percent = percentage(serv_reqs, total_reqs); print_val_perc_table_row(outfile, "Serviced requests", UNIT_REQUESTS, percent, "%lu", serv_reqs); print_val_perc_table_section(outfile, "Total requests", UNIT_REQUESTS, total_reqs ? 100.0f : 0.0f, "%lu", total_reqs); } static void print_block_section(const struct ocf_stats_block *stats_4k, const char *dev_name, FILE *outfile, ocf_cache_line_size_t cache_line_size) { uint64_t total_4k; float percent; total_4k = stats_4k->read + stats_4k->write; percent = percentage(stats_4k->read, total_4k); print_val_perc_table_section(outfile, make_row_title("Reads from", dev_name), UNIT_BLOCKS, percent, "%lu", stats_4k->read); percent = percentage(stats_4k->write, total_4k); print_val_perc_table_row(outfile, make_row_title("Writes to", dev_name), UNIT_BLOCKS, percent, "%lu", stats_4k->write); print_val_perc_table_row(outfile, make_row_title("Total to/from", dev_name), UNIT_BLOCKS, total_4k ? 100.0f : 0.0f, "%lu", total_4k); } static struct ocf_stats_block convert_block_stats_to_4k( const struct ocf_stats_block *stats) { struct ocf_stats_block stats_4k; stats_4k.read = bytes_to_4k(stats->read); stats_4k.write = bytes_to_4k(stats->write); return stats_4k; } void print_block_stats(const struct ocf_stats_core *exp_obj_stats, FILE *outfile, ocf_cache_line_size_t cache_line_size) { struct ocf_stats_block cache_volume_stats_4k = convert_block_stats_to_4k(&exp_obj_stats->cache_volume); struct ocf_stats_block core_volume_stats_4k = convert_block_stats_to_4k(&exp_obj_stats->core_volume); struct ocf_stats_block core_stats_4k = convert_block_stats_to_4k(&exp_obj_stats->core); print_table_header(outfile, 4, "Block statistics", "Count", "%", "[Units]"); print_block_section(&core_volume_stats_4k, "core", outfile, cache_line_size); print_block_section(&cache_volume_stats_4k, "cache", outfile, cache_line_size); print_block_section(&core_stats_4k, "exported object", outfile, cache_line_size); } static void print_error_section(const struct ocf_stats_error *stats, const char *section_name, FILE *outfile) { uint64_t total = 0; float percent; total = stats->read + stats->write; percent = percentage(stats->read, total); print_val_perc_table_section(outfile, make_row_title(section_name , "read errors"), UNIT_REQUESTS, percent, "%u", stats->read); percent = percentage(stats->write, total); print_val_perc_table_row(outfile, make_row_title(section_name, "write errors"), UNIT_REQUESTS, percent, "%u", stats->write); print_val_perc_table_row(outfile, make_row_title(section_name, "total errors"), UNIT_REQUESTS, total ? 100.0f : 0.0f, "%lu", total); } static void print_error_stats_total(const struct ocf_stats_error *cache_stats, const struct ocf_stats_error *core_stats, FILE *outfile) { uint64_t total; total = cache_stats->read + cache_stats->write + core_stats->read + core_stats->write; print_val_perc_table_section(outfile, "Total errors", UNIT_REQUESTS, total ? 100.0f : 0.0f, "%lu", total); } static void print_error_stats(const struct ocf_stats_core *exp_obj_stats, FILE *outfile) { print_table_header(outfile, 4, "Error statistics", "Count", "%", "[Units]"); print_error_section(&exp_obj_stats->cache_errors, "Cache", outfile); print_error_section(&exp_obj_stats->core_errors, "Core", outfile); print_error_stats_total(&exp_obj_stats->cache_errors, &exp_obj_stats->core_errors, outfile); } void cache_stats_core_counters(const struct kcas_core_info *info, uint32_t cache_size, uint32_t cache_occupancy, unsigned int stats_filters, FILE *outfile, ocf_cache_line_size_t cache_line_size) { const struct ocf_stats_core *stats = &info->stats; begin_record(outfile); if (stats_filters & STATS_FILTER_CONF) { print_core_conf(info, cache_size, outfile, cache_line_size); } if (stats_filters & STATS_FILTER_USAGE) { print_core_usage(stats, cache_size, cache_occupancy, outfile, cache_line_size); } if (stats_filters & STATS_FILTER_REQ) { print_req_stats(stats, outfile); } if (stats_filters & STATS_FILTER_BLK) { print_block_stats(stats, outfile, cache_line_size); } if (stats_filters & STATS_FILTER_ERR) { print_error_stats(stats, outfile); } } static void print_stats_ioclass_conf(const struct kcas_io_class* io_class, FILE* outfile) { print_kv_pair(outfile, "IO class ID", "%d", io_class->class_id); print_kv_pair(outfile, "IO class name", "%s", io_class->info.name); if (-1 == io_class->info.priority) { print_kv_pair(outfile, "Eviction priority", "Pinned"); } else { print_kv_pair(outfile, "Eviction priority", "%d", io_class->info.priority); } print_kv_pair(outfile, "Selective allocation", "%s", io_class->info.cache_mode != ocf_cache_mode_pt ? "Yes" : "No"); } static void print_stats_ioclass_usage(uint32_t part_id, const struct ocf_stats_io_class* part_stats, const struct ocf_stats_io_class* denominators, FILE *outfile, uint32_t cache_size, ocf_cache_line_size_t cache_line_size) { float percent; uint64_t clean; print_table_header(outfile, 4, "Usage statistics", "Count", "%", "[Units]"); percent = percentage(part_stats->occupancy_clines, cache_size); print_val_perc_table_section(outfile, "Occupancy", UNIT_BLOCKS, percent, "%ld", cache_line_in_4k(part_stats->occupancy_clines, cache_line_size)); /* Occupancy, dirty, etc. information. */ /* For now free stat should be printed for the unclassified IO class. */ if (IOCLASS_UNCLASSIFIED == part_id) { print_val_perc_table_row(outfile, "Free", UNIT_BLOCKS, 100.0f, "%ld", cache_line_in_4k(part_stats->free_clines, cache_line_size)); } else { print_val_perc_table_row(outfile, "Free", UNIT_BLOCKS, 0.0f, "%d", 0); } clean = part_stats->occupancy_clines - part_stats->dirty_clines; percent = percentage(clean, part_stats->occupancy_clines); print_val_perc_table_row(outfile, "Clean", UNIT_BLOCKS, percent, "%ld", cache_line_in_4k(clean, cache_line_size)); percent = percentage(part_stats->dirty_clines, part_stats->occupancy_clines); print_val_perc_table_row(outfile, "Dirty", UNIT_BLOCKS, percent, "%ld", cache_line_in_4k(part_stats->dirty_clines, cache_line_size)); } static void print_stats_ioclass_req(const struct ocf_stats_io_class* part_stats, const struct ocf_stats_io_class* denominators, FILE *outfile, uint64_t req_grand_total) { const struct ocf_stats_req *req_stats; float percent; uint64_t hits; uint64_t serv_reqs = 0; uint64_t total_reqs = 0; print_table_header(outfile, 4, "Request statistics", "Count", "%", "[Units]"); /* Handling read operations. */ req_stats = &part_stats->read_reqs; hits = req_stats->total - (req_stats->partial_miss + req_stats->full_miss); percent = percentage(hits, req_grand_total); print_val_perc_table_section(outfile, "Read hits", UNIT_REQUESTS, percent, "%ld", hits); percent = percentage(req_stats->partial_miss, req_grand_total); print_val_perc_table_row(outfile, "Read partial misses", UNIT_REQUESTS, percent, "%ld", req_stats->partial_miss); percent = percentage(req_stats->full_miss, req_grand_total); print_val_perc_table_row(outfile, "Read full misses", UNIT_REQUESTS, percent, "%ld", req_stats->full_miss); percent = percentage(req_stats->total, req_grand_total); print_val_perc_table_row(outfile, "Read total", UNIT_REQUESTS, percent, "%ld", req_stats->total); /* Handling write operations. */ req_stats = &part_stats->write_reqs; hits = req_stats->total - (req_stats->partial_miss + req_stats->full_miss); percent = percentage(hits, req_grand_total); print_val_perc_table_section(outfile, "Write hits", UNIT_REQUESTS, percent, "%ld", hits); percent = percentage(req_stats->partial_miss, req_grand_total); print_val_perc_table_row(outfile, "Write partial misses", UNIT_REQUESTS, percent, "%ld", req_stats->partial_miss); percent = percentage(req_stats->full_miss, req_grand_total); print_val_perc_table_row(outfile, "Write full misses", UNIT_REQUESTS, percent, "%ld", req_stats->full_miss); percent = percentage(req_stats->total, req_grand_total); print_val_perc_table_row(outfile, "Write total", UNIT_REQUESTS, percent, "%ld", req_stats->total); /* Pass-Through requests. */ percent = percentage(part_stats->read_reqs.pass_through, req_grand_total); print_val_perc_table_section(outfile, "Pass-Through reads", UNIT_REQUESTS, percent, "%lu", part_stats->read_reqs.pass_through); percent = percentage(part_stats->write_reqs.pass_through, req_grand_total); print_val_perc_table_row(outfile, "Pass-Through writes", UNIT_REQUESTS, percent, "%lu", part_stats->write_reqs.pass_through); /* Summary. */ serv_reqs += part_stats->read_reqs.total; serv_reqs += part_stats->write_reqs.total; total_reqs = serv_reqs + part_stats->read_reqs.pass_through + part_stats->write_reqs.pass_through; percent = percentage(serv_reqs, req_grand_total); print_val_perc_table_row(outfile, "Serviced requests", UNIT_REQUESTS, percent, "%lu", serv_reqs); percent = percentage(total_reqs, req_grand_total); print_val_perc_table_section(outfile, "Total requests", UNIT_REQUESTS, percent, "%lu", total_reqs); } static void print_stats_ioclass_blk(const struct ocf_stats_io_class* part_stats, const struct ocf_stats_io_class* denominators, FILE *outfile, ocf_cache_line_size_t cache_line_size) { float percent; print_table_header(outfile, 4, "Block statistics", "Count", "%", "[Units]"); /* Handling read operations. */ percent = percentage(part_stats->blocks.read, denominators->blocks.read); print_val_perc_table_section(outfile, "Blocks reads", UNIT_BLOCKS, percent, "%ld", bytes_to_4k(part_stats->blocks.read)); /* Handling write operations. */ percent = percentage(part_stats->blocks.write, denominators->blocks.write); print_val_perc_table_section(outfile, "Blocks writes", UNIT_BLOCKS, percent, "%ld", bytes_to_4k(part_stats->blocks.write)); } /** * print statistics regarding single io class (partition) */ void print_stats_ioclass(const struct kcas_cache_info *cache_info, const struct kcas_io_class *io_class, FILE *outfile, unsigned int stats_filters, struct ocf_stats_io_class *denominators, uint64_t req_grand_total, ocf_cache_line_size_t cache_line_size) { const struct ocf_stats_io_class *part_stats; uint32_t part_id; part_id = io_class->class_id; part_stats = &io_class->stats; begin_record(outfile); if (stats_filters & STATS_FILTER_CONF) { print_stats_ioclass_conf(io_class, outfile); } if (stats_filters & STATS_FILTER_USAGE) { print_stats_ioclass_usage(part_id, part_stats, denominators, outfile, cache_info->info.size, cache_line_size); } if (stats_filters & STATS_FILTER_REQ) { print_stats_ioclass_req(part_stats, denominators, outfile, req_grand_total); } if (stats_filters & STATS_FILTER_BLK) { print_stats_ioclass_blk(part_stats, denominators, outfile, cache_line_size); } } static int read_io_class_stats(int ctrl_fd, int cache_id, int core_id, int part_id, struct kcas_io_class *io_class_tmp, struct kcas_io_class *io_class_out) { memset(io_class_tmp, 0, sizeof(*io_class_tmp)); io_class_tmp->cache_id = cache_id; io_class_tmp->class_id = part_id; if (core_id != OCF_CORE_ID_INVALID) { io_class_tmp->core_id = core_id; io_class_tmp->get_stats = 1; } if (ioctl(ctrl_fd, KCAS_IOCTL_PARTITION_STATS, io_class_tmp) < 0) { io_class_out->ext_err_code = io_class_tmp->ext_err_code; return FAILURE; } io_class_out->ext_err_code = io_class_tmp->ext_err_code; strncpy_s(io_class_out->info.name, sizeof(io_class_out->info.name), io_class_tmp->info.name, sizeof(io_class_tmp->info.name) - 1); io_class_out->class_id = io_class_tmp->class_id; io_class_out->info.priority = io_class_tmp->info.priority; io_class_out->info.cache_mode = io_class_tmp->info.cache_mode; return SUCCESS; } static inline void accum_block_stats(struct ocf_stats_block *to, const struct ocf_stats_block *from) { to->read += from->read; to->write += from->write; } static inline void accum_req_stats(struct ocf_stats_req *to, const struct ocf_stats_req *from) { to->full_miss += from->full_miss; to->partial_miss += from->partial_miss; to->total += from->total; to->pass_through += from->pass_through; } /** * @brief print per-io-class statistics for all configured io classes * */ int cache_stats_ioclasses(int ctrl_fd, const struct kcas_cache_info *cache_info, unsigned int cache_id, unsigned int core_id, int io_class_id, FILE *outfile, unsigned int stats_filters) { int i, j, _core_id; struct ocf_stats_io_class denominators; struct ocf_stats_io_class* part_stats_cum; struct ocf_stats_io_class* part_stats_core; struct kcas_io_class io_class_new[OCF_IO_CLASS_MAX] = {}; struct kcas_io_class io_class_tmp; uint64_t req_grand_total = 0; memset(&denominators, 0, sizeof(denominators)); if (-1 != io_class_id && io_class_id >= OCF_IO_CLASS_MAX) { cas_printf(LOG_ERR, "Partition %d does not exists\n", io_class_id); return FAILURE; } for (i = 0; i < OCF_IO_CLASS_MAX; ++i) { /* print stats for each ioclass */ if (!cache_info->info.core_count) { if (read_io_class_stats(ctrl_fd, cache_id, 0, i, &io_class_tmp, &io_class_new[i])) { if (io_class_new[i].ext_err_code == OCF_ERR_IO_CLASS_NOT_EXIST) { continue; } cas_printf(LOG_ERR, "Error while retrieving stats for partition %d\n", i); print_err(io_class_new[i].ext_err_code); goto cleanup; } } else { for (j = 0; j < cache_info->info.core_count; ++j) { _core_id = cache_info->core_id[j]; if (core_id != OCF_CORE_ID_INVALID && core_id != _core_id) { continue; } if (read_io_class_stats(ctrl_fd, cache_id, _core_id, i, &io_class_tmp, &io_class_new[i])) { if (io_class_new[i].ext_err_code == OCF_ERR_IO_CLASS_NOT_EXIST) { continue; } cas_printf(LOG_ERR, "Error while retrieving stats for partition %d, core %d\n", i, core_id); print_err(io_class_new[i].ext_err_code); goto cleanup; } part_stats_cum = &io_class_new[i].stats; part_stats_core = &io_class_tmp.stats; part_stats_cum->free_clines = part_stats_core->free_clines; part_stats_cum->occupancy_clines += part_stats_core->occupancy_clines; part_stats_cum->dirty_clines += part_stats_core->dirty_clines; accum_block_stats(&part_stats_cum->blocks, &part_stats_core->blocks); accum_req_stats(&part_stats_cum->read_reqs, &part_stats_core->read_reqs); accum_req_stats(&part_stats_cum->write_reqs, &part_stats_core->write_reqs); } } } for (i = 0; i < OCF_IO_CLASS_MAX; ++i) { if (io_class_new[i].ext_err_code == OCF_ERR_IO_CLASS_NOT_EXIST) { continue; } const struct ocf_stats_io_class *ps = &io_class_new[i].stats; denominators.occupancy_clines += ps->occupancy_clines; denominators.dirty_clines += ps->dirty_clines; accum_block_stats(&denominators.blocks, &ps->blocks); accum_req_stats(&denominators.read_reqs, &ps->read_reqs); accum_req_stats(&denominators.write_reqs, &ps->write_reqs); } req_grand_total += denominators.read_reqs.total; req_grand_total += denominators.read_reqs.pass_through; req_grand_total += denominators.write_reqs.total; req_grand_total += denominators.write_reqs.pass_through; if (-1 == io_class_id) { for (i = 0; i < OCF_IO_CLASS_MAX; ++i) { if (io_class_new[i].ext_err_code == OCF_ERR_IO_CLASS_NOT_EXIST) { continue; } print_stats_ioclass(cache_info, &io_class_new[i], outfile, stats_filters, &denominators, req_grand_total, cache_info->info.cache_line_size / KiB); } } else { if (io_class_new[io_class_id].ext_err_code == OCF_ERR_IO_CLASS_NOT_EXIST) { cas_printf(LOG_ERR, "Partition %d does not exists\n", io_class_id); return FAILURE; } print_stats_ioclass(cache_info, &io_class_new[io_class_id], outfile, stats_filters, &denominators, req_grand_total, cache_info->info.cache_line_size / KiB); } return SUCCESS; cleanup: close(ctrl_fd); if (outfile != stdout) { fclose(outfile); } return FAILURE; } static inline void accum_error_stats(struct ocf_stats_error *to, const struct ocf_stats_error *from) { to->read += from->read; to->write += from->write; } int cache_stats_cores(int ctrl_fd, const struct kcas_cache_info *cache_info, unsigned int cache_id, unsigned int core_id, int io_class_id, FILE *outfile, unsigned int stats_filters) { int i; int _core_id; uint32_t cache_size; ocf_cache_line_size_t cache_line_size; struct kcas_core_info core_info; for (i = 0; i < cache_info->info.core_count; ++i) { /* if user only requested stats pertaining to a specific core, skip all other cores */ _core_id = cache_info->core_id[i]; if ((core_id != OCF_CORE_ID_INVALID) && (core_id != _core_id)) { continue; } /* call function to print stats */ if (get_core_info(ctrl_fd, cache_id, _core_id, &core_info)) { cas_printf(LOG_ERR, "Error while retrieving stats for core %d\n", _core_id); print_err(core_info.ext_err_code); return FAILURE; } cache_size = cache_info->info.size; cache_line_size = cache_info->info.cache_line_size / KiB; cache_stats_core_counters(&core_info, cache_size, cache_info->info.occupancy, stats_filters, outfile, cache_line_size); } return SUCCESS; } int cache_stats_conf(int ctrl_fd, const struct kcas_cache_info *cache_info, unsigned int cache_id, FILE *outfile, unsigned int stats_filters) { float flush_progress = 0; float value; const char *units; long unsigned int cache_size; const char *cache_path; char dev_path[MAX_STR_LEN]; int inactive_cores; if (get_dev_path(cache_info->cache_path_name, dev_path, sizeof(dev_path)) != SUCCESS) cache_path = cache_info->cache_path_name; else cache_path = dev_path; flush_progress = calculate_flush_progress(cache_info->info.dirty, cache_info->info.flushed); print_kv_pair(outfile, "Cache Id", "%d", cache_info->cache_id); cache_size = cache_line_in_4k(cache_info->info.size, cache_info->info.cache_line_size / KiB); print_kv_pair(outfile, "Cache Size", "%lu, [4KiB Blocks], %.2f, [GiB]", cache_size, (float) cache_size * (4 * KiB) / GiB); print_kv_pair(outfile, "Cache Device", "%s", cache_path); print_kv_pair(outfile, "Core Devices", "%d", cache_info->info.core_count); inactive_cores = get_inactive_core_count(cache_info); if (inactive_cores < 0) return FAILURE; print_kv_pair(outfile, "Inactive Core Devices", "%d", inactive_cores); print_kv_pair(outfile, "Write Policy", "%s%s", (flush_progress && cache_info->info.cache_mode != ocf_cache_mode_wb) ? "wb->" : "", cache_mode_to_name(cache_info->info.cache_mode)); print_kv_pair(outfile, "Eviction Policy", "%s", eviction_policy_to_name(cache_info->info.eviction_policy)); print_kv_pair(outfile, "Cleaning Policy", "%s", cleaning_policy_to_name(cache_info->info.cleaning_policy)); print_kv_pair(outfile, "Cache line size", "%llu, [KiB]", cache_info->info.cache_line_size / KiB); metadata_memory_footprint(cache_info->info.metadata_footprint, &value, &units); print_kv_pair(outfile, "Metadata Memory Footprint", "%.1f, [%s]", value, units); print_kv_pair_time(outfile, "Dirty for", cache_info->info.dirty_for); print_kv_pair(outfile, "Metadata Mode", "%s", metadata_mode_to_name(cache_info->metadata_mode)); if (flush_progress) { print_kv_pair(outfile, "Status", "%s (%3.1f %%)", "Flushing", flush_progress); } else { print_kv_pair(outfile, "Status", "%s", get_cache_state_name(cache_info->info.state)); } return SUCCESS; } int cache_stats_usage(int ctrl_fd, const struct kcas_cache_info *cache_info, unsigned int cache_id, FILE* outfile) { print_usage_header(outfile); print_val_perc_table_row(outfile, "Occupancy", UNIT_BLOCKS, percentage(cache_info->info.occupancy, cache_info->info.size), "%lu", cache_line_in_4k(cache_info->info.occupancy, cache_info->info.cache_line_size / KiB)); print_val_perc_table_row(outfile, "Free", UNIT_BLOCKS, percentage(cache_info->info.size - cache_info->info.occupancy, cache_info->info.size), "%lu", cache_line_in_4k(cache_info->info.size - cache_info->info.occupancy, cache_info->info.cache_line_size / KiB)); print_val_perc_table_row(outfile, "Clean", UNIT_BLOCKS, percentage(cache_info->info.occupancy - cache_info->info.dirty, cache_info->info.occupancy), "%lu", cache_line_in_4k(cache_info->info.occupancy - cache_info->info.dirty, cache_info->info.cache_line_size / KiB)); print_val_perc_table_row(outfile, "Dirty", UNIT_BLOCKS, percentage(cache_info->info.dirty, cache_info->info.occupancy), "%lu", cache_line_in_4k(cache_info->info.dirty, cache_info->info.cache_line_size / KiB)); return SUCCESS; } int cache_stats_inactive_usage(int ctrl_fd, const struct kcas_cache_info *cache_info, unsigned int cache_id, FILE* outfile) { print_table_header(outfile, 4, "Inactive usage statistics", "Count", "%", "[Units]"); print_val_perc_table_row(outfile, "Inactive Occupancy", UNIT_BLOCKS, percentage(cache_info->info.inactive.occupancy, cache_info->info.size), "%lu", cache_line_in_4k(cache_info->info.inactive.occupancy, cache_info->info.cache_line_size / KiB)); print_val_perc_table_row(outfile, "Inactive Clean", UNIT_BLOCKS, percentage(cache_info->info.inactive.occupancy - cache_info->info.inactive.dirty, cache_info->info.occupancy), "%lu", cache_line_in_4k(cache_info->info.inactive.occupancy - cache_info->info.inactive.dirty, cache_info->info.cache_line_size / KiB)); print_val_perc_table_row(outfile, "Inactive Dirty", UNIT_BLOCKS, percentage(cache_info->info.inactive.dirty, cache_info->info.occupancy), "%lu", cache_line_in_4k(cache_info->info.inactive.dirty, cache_info->info.cache_line_size / KiB)); return SUCCESS; } int cache_stats_counters(int ctrl_fd, const struct kcas_cache_info *cache_info, unsigned int cache_id, FILE *outfile, unsigned int stats_filters) { int i; int _core_id; struct ocf_stats_core *stats; struct ocf_stats_core total_stats; struct kcas_core_info core_info; struct ocf_stats_error total_cache_errors, total_core_errors; memset(&total_stats, 0, sizeof(total_stats)); memset(&total_cache_errors, 0, sizeof(total_cache_errors)); memset(&total_core_errors, 0, sizeof(total_core_errors)); for (i = 0; i < cache_info->info.core_count; ++i) { /* if user only requested stats pertaining to a specific core, skip all other cores */ _core_id = cache_info->core_id[i]; /* call function to print stats */ if (get_core_info(ctrl_fd, cache_id, _core_id, &core_info)) { cas_printf(LOG_ERR, "Error while retrieving stats for core %d\n", _core_id); print_err(core_info.ext_err_code); return FAILURE; } stats = &core_info.stats; /* Convert block stats to 4k before adding them up. This way sum of block stats for cores is consistent with cache stats */ stats->cache_volume = convert_block_stats_to_4k(&stats->cache_volume); stats->core_volume = convert_block_stats_to_4k(&stats->core_volume); stats->core = convert_block_stats_to_4k(&stats->core); accum_block_stats(&total_stats.cache_volume, &stats->cache_volume); accum_block_stats(&total_stats.core_volume, &stats->core_volume); accum_block_stats(&total_stats.core, &stats->core); accum_req_stats(&total_stats.read_reqs, &stats->read_reqs); accum_req_stats(&total_stats.write_reqs, &stats->write_reqs); accum_error_stats(&total_cache_errors, &stats->cache_errors); accum_error_stats(&total_core_errors, &stats->core_errors); } /* Totals for requests stats. */ if (stats_filters & STATS_FILTER_REQ) { print_req_stats(&total_stats, outfile); } /* Totals for blocks stats. */ if (stats_filters & STATS_FILTER_BLK) { print_table_header(outfile, 4, "Block statistics", "Count", "%", "[Units]"); print_block_section(&total_stats.core_volume, "core(s)", outfile, cache_info->info.cache_line_size / KiB); print_block_section(&total_stats.cache_volume, "cache", outfile, cache_info->info.cache_line_size / KiB); print_block_section(&total_stats.core, "exported object(s)", outfile, cache_info->info.cache_line_size / KiB); } /* Totals for error stats. */ if (stats_filters & STATS_FILTER_ERR) { print_table_header(outfile, 4, "Error statistics", "Count", "%", "[Units]"); print_error_section(&total_cache_errors, "Cache", outfile); print_error_section(&total_core_errors, "Core", outfile); print_error_stats_total(&total_cache_errors, &total_core_errors, outfile); } return SUCCESS; } struct stats_printout_ctx { FILE *intermediate; FILE *out; int type; int result; }; void *stats_printout(void *ctx) { struct stats_printout_ctx *spc = ctx; if (stat_format_output(spc->intermediate, spc->out, spc->type)) { cas_printf(LOG_ERR, "An error occured during statistics formatting.\n"); spc->result = FAILURE; } else { spc->result = SUCCESS; } return 0; } bool _usage_stats_is_valid(struct kcas_cache_info *cmd_info) { return (cmd_info->info.size >= cmd_info->info.occupancy); } /** * @brief print cache statistics in various variants * * this routine implements -P (--stats) subcommand of casadm. * @param cache_id id of a cache, to which stats query pertains * @param stats_filters subset of statistics to be displayed. If filters are not * specified STATS_FILTER_DEFAULT are displayd. * @param fpath path to an output CSV file to which statistics shall be printed. single "-" * can be passed as a path, to generate CSV to stdout. Henceforth non-NULL value of * fpath is a sign that stats shall be printed in CSV-format, and NULL value will] * cause stats to be printed in pretty tables. * * @return SUCCESS upon successful printing of statistic. FAILURE if any error happens */ int cache_status(unsigned int cache_id, unsigned int core_id, int io_class_id, unsigned int stats_filters, unsigned int output_format) { int ctrl_fd, i; int ret = SUCCESS; int attempt_no = 0; struct kcas_cache_info cache_info; ctrl_fd = open_ctrl_device(); if (ctrl_fd < 0) { print_err(KCAS_ERR_SYSTEM); return FAILURE; } /** * * Procedure of printing out statistics is as follows: * * * statistics_model.c (retrieve structures from kernel, don't do formatting) * | * v * abstract CSV notation with prefixes (as a temporary file) * | * v * statistics_view (parse basic csv notation, generate proper output) * | * v * desired output format * */ /* 1 is writing end, 0 is reading end of a pipe */ FILE *intermediate_file[2]; if (create_pipe_pair(intermediate_file)) { cas_printf(LOG_ERR,"Failed to create unidirectional pipe.\n"); close(ctrl_fd); return FAILURE; } /** * printing in statistics will be performed in separate * thread, so that we can interleave statistics collecting * and formatting tables */ struct stats_printout_ctx printout_ctx; printout_ctx.intermediate = intermediate_file[0]; printout_ctx.out = stdout; printout_ctx.type = (OUTPUT_FORMAT_CSV == output_format ? CSV : TEXT); pthread_t thread; pthread_create(&thread, 0, stats_printout, &printout_ctx); memset(&cache_info, 0, sizeof(cache_info)); cache_info.cache_id = cache_id; do { if (0 != attempt_no) { usleep(300 * 1000); } if (ioctl(ctrl_fd, KCAS_IOCTL_CACHE_INFO, &cache_info) < 0) { cas_printf(LOG_ERR, "Cache Id %d not running\n", cache_id); ret = FAILURE; goto cleanup; } /* Check if core exists in cache */ if (core_id != OCF_CORE_ID_INVALID) { for (i = 0; i < cache_info.info.core_count; ++i) { if (core_id == cache_info.core_id[i]) { break; } } if (i == cache_info.info.core_count) { cas_printf(LOG_ERR, "No such core device in cache.\n"); ret = FAILURE; goto cleanup; } } attempt_no++; } while (false == _usage_stats_is_valid(&cache_info) && (attempt_no < ALLOWED_NUMBER_OF_ATTEMPTS)); if (stats_filters & STATS_FILTER_IOCLASS) { if (cache_stats_ioclasses(ctrl_fd, &cache_info, cache_id, core_id, io_class_id, intermediate_file[1], stats_filters)) { return FAILURE; } } else if (core_id == OCF_CORE_ID_INVALID) { begin_record(intermediate_file[1]); if (stats_filters & STATS_FILTER_CONF) { if (cache_stats_conf(ctrl_fd, &cache_info, cache_id, intermediate_file[1], stats_filters)) { ret = FAILURE; goto cleanup; } } if (stats_filters & STATS_FILTER_USAGE) { if (cache_stats_usage(ctrl_fd, &cache_info, cache_id, intermediate_file[1])) { ret = FAILURE; goto cleanup; } } if ((cache_info.info.state & (1 << ocf_cache_state_incomplete)) && stats_filters & STATS_FILTER_USAGE) { if (cache_stats_inactive_usage(ctrl_fd, &cache_info, cache_id, intermediate_file[1])) { ret = FAILURE; goto cleanup; } } if (stats_filters & STATS_FILTER_COUNTERS) { if (cache_stats_counters(ctrl_fd, &cache_info, cache_id, intermediate_file[1], stats_filters)) { ret = FAILURE; goto cleanup; } } } else { /* print per core statistics. this may include: * - core header * - core counters * - core per io class statistics * * depending on which set of statistics is enabled via -f/-d switches. */ if (cache_stats_cores(ctrl_fd, &cache_info, cache_id, core_id, io_class_id, intermediate_file[1], stats_filters)) { ret = FAILURE; goto cleanup; } } cleanup: close(ctrl_fd); fclose(intermediate_file[1]); pthread_join(thread, 0); if (printout_ctx.result) { ret = 1; } fclose(intermediate_file[0]); return ret; }