// SPDX-License-Identifier: GPL-2.0 /* * perf iostat * * Copyright (C) 2020, Intel Corporation * * Authors: Alexander Antonov <alexander.antonov@linux.intel.com> */ #include <api/fs/fs.h> #include <linux/kernel.h> #include <linux/err.h> #include <linux/zalloc.h> #include <limits.h> #include <stdio.h> #include <string.h> #include <errno.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <dirent.h> #include <unistd.h> #include <stdlib.h> #include <regex.h> #include "util/cpumap.h" #include "util/debug.h" #include "util/iostat.h" #include "util/counts.h" #include "path.h" #ifndef MAX_PATH #define MAX_PATH 1024 #endif #define UNCORE_IIO_PMU_PATH "devices/uncore_iio_%d" #define SYSFS_UNCORE_PMU_PATH "%s/"UNCORE_IIO_PMU_PATH #define PLATFORM_MAPPING_PATH UNCORE_IIO_PMU_PATH"/die%d" /* * Each metric requiries one IIO event which increments at every 4B transfer * in corresponding direction. The formulas to compute metrics are generic: * #EventCount * 4B / (1024 * 1024) */ static const char * const iostat_metrics[] = { "Inbound Read(MB)", "Inbound Write(MB)", "Outbound Read(MB)", "Outbound Write(MB)", }; static inline int iostat_metrics_count(void) { return sizeof(iostat_metrics) / sizeof(char *); } static const char *iostat_metric_by_idx(int idx) { return *(iostat_metrics + idx % iostat_metrics_count()); } struct iio_root_port { u32 domain; u8 bus; u8 die; u8 pmu_idx; int idx; }; struct iio_root_ports_list { struct iio_root_port **rps; int nr_entries; }; static struct iio_root_ports_list *root_ports; static void iio_root_port_show(FILE *output, const struct iio_root_port * const rp) { if (output && rp) fprintf(output, "S%d-uncore_iio_%d<%04x:%02x>\n", rp->die, rp->pmu_idx, rp->domain, rp->bus); } static struct iio_root_port *iio_root_port_new(u32 domain, u8 bus, u8 die, u8 pmu_idx) { struct iio_root_port *p = calloc(1, sizeof(*p)); if (p) { p->domain = domain; p->bus = bus; p->die = die; p->pmu_idx = pmu_idx; } return p; } static void iio_root_ports_list_free(struct iio_root_ports_list *list) { int idx; if (list) { for (idx = 0; idx < list->nr_entries; idx++) zfree(&list->rps[idx]); zfree(&list->rps); free(list); } } static struct iio_root_port *iio_root_port_find_by_notation( const struct iio_root_ports_list * const list, u32 domain, u8 bus) { int idx; struct iio_root_port *rp; if (list) { for (idx = 0; idx < list->nr_entries; idx++) { rp = list->rps[idx]; if (rp && rp->domain == domain && rp->bus == bus) return rp; } } return NULL; } static int iio_root_ports_list_insert(struct iio_root_ports_list *list, struct iio_root_port * const rp) { struct iio_root_port **tmp_buf; if (list && rp) { rp->idx = list->nr_entries++; tmp_buf = realloc(list->rps, list->nr_entries * sizeof(*list->rps)); if (!tmp_buf) { pr_err("Failed to realloc memory\n"); return -ENOMEM; } tmp_buf[rp->idx] = rp; list->rps = tmp_buf; } return 0; } static int iio_mapping(u8 pmu_idx, struct iio_root_ports_list * const list) { char *buf; char path[MAX_PATH]; u32 domain; u8 bus; struct iio_root_port *rp; size_t size; int ret; for (int die = 0; die < cpu__max_node(); die++) { scnprintf(path, MAX_PATH, PLATFORM_MAPPING_PATH, pmu_idx, die); if (sysfs__read_str(path, &buf, &size) < 0) { if (pmu_idx) goto out; pr_err("Mode iostat is not supported\n"); return -1; } ret = sscanf(buf, "%04x:%02hhx", &domain, &bus); free(buf); if (ret != 2) { pr_err("Invalid mapping data: iio_%d; die%d\n", pmu_idx, die); return -1; } rp = iio_root_port_new(domain, bus, die, pmu_idx); if (!rp || iio_root_ports_list_insert(list, rp)) { free(rp); return -ENOMEM; } } out: return 0; } static u8 iio_pmu_count(void) { u8 pmu_idx = 0; char path[MAX_PATH]; const char *sysfs = sysfs__mountpoint(); if (sysfs) { for (;; pmu_idx++) { snprintf(path, sizeof(path), SYSFS_UNCORE_PMU_PATH, sysfs, pmu_idx); if (access(path, F_OK) != 0) break; } } return pmu_idx; } static int iio_root_ports_scan(struct iio_root_ports_list **list) { int ret = -ENOMEM; struct iio_root_ports_list *tmp_list; u8 pmu_count = iio_pmu_count(); if (!pmu_count) { pr_err("Unsupported uncore pmu configuration\n"); return -1; } tmp_list = calloc(1, sizeof(*tmp_list)); if (!tmp_list) goto err; for (u8 pmu_idx = 0; pmu_idx < pmu_count; pmu_idx++) { ret = iio_mapping(pmu_idx, tmp_list); if (ret) break; } err: if (!ret) *list = tmp_list; else iio_root_ports_list_free(tmp_list); return ret; } static int iio_root_port_parse_str(u32 *domain, u8 *bus, char *str) { int ret; regex_t regex; /* * Expected format domain:bus: * Valid domain range [0:ffff] * Valid bus range [0:ff] * Example: 0000:af, 0:3d, 01:7 */ regcomp(®ex, "^([a-f0-9A-F]{1,}):([a-f0-9A-F]{1,2})", REG_EXTENDED); ret = regexec(®ex, str, 0, NULL, 0); if (ret || sscanf(str, "%08x:%02hhx", domain, bus) != 2) pr_warning("Unrecognized root port format: %s\n" "Please use the following format:\n" "\t [domain]:[bus]\n" "\t for example: 0000:3d\n", str); regfree(®ex); return ret; } static int iio_root_ports_list_filter(struct iio_root_ports_list **list, const char *filter) { char *tok, *tmp, *filter_copy = NULL; struct iio_root_port *rp; u32 domain; u8 bus; int ret = -ENOMEM; struct iio_root_ports_list *tmp_list = calloc(1, sizeof(*tmp_list)); if (!tmp_list) goto err; filter_copy = strdup(filter); if (!filter_copy) goto err; for (tok = strtok_r(filter_copy, ",", &tmp); tok; tok = strtok_r(NULL, ",", &tmp)) { if (!iio_root_port_parse_str(&domain, &bus, tok)) { rp = iio_root_port_find_by_notation(*list, domain, bus); if (rp) { (*list)->rps[rp->idx] = NULL; ret = iio_root_ports_list_insert(tmp_list, rp); if (ret) { free(rp); goto err; } } else if (!iio_root_port_find_by_notation(tmp_list, domain, bus)) pr_warning("Root port %04x:%02x were not found\n", domain, bus); } } if (tmp_list->nr_entries == 0) { pr_err("Requested root ports were not found\n"); ret = -EINVAL; } err: iio_root_ports_list_free(*list); if (ret) iio_root_ports_list_free(tmp_list); else *list = tmp_list; free(filter_copy); return ret; } static int iostat_event_group(struct evlist *evl, struct iio_root_ports_list *list) { int ret; int idx; const char *iostat_cmd_template = "{uncore_iio_%x/event=0x83,umask=0x04,ch_mask=0xF,fc_mask=0x07/,\ uncore_iio_%x/event=0x83,umask=0x01,ch_mask=0xF,fc_mask=0x07/,\ uncore_iio_%x/event=0xc0,umask=0x04,ch_mask=0xF,fc_mask=0x07/,\ uncore_iio_%x/event=0xc0,umask=0x01,ch_mask=0xF,fc_mask=0x07/}"; const int len_template = strlen(iostat_cmd_template) + 1; struct evsel *evsel = NULL; int metrics_count = iostat_metrics_count(); char *iostat_cmd = calloc(len_template, 1); if (!iostat_cmd) return -ENOMEM; for (idx = 0; idx < list->nr_entries; idx++) { sprintf(iostat_cmd, iostat_cmd_template, list->rps[idx]->pmu_idx, list->rps[idx]->pmu_idx, list->rps[idx]->pmu_idx, list->rps[idx]->pmu_idx); ret = parse_event(evl, iostat_cmd); if (ret) goto err; } evlist__for_each_entry(evl, evsel) { evsel->priv = list->rps[evsel->core.idx / metrics_count]; } list->nr_entries = 0; err: iio_root_ports_list_free(list); free(iostat_cmd); return ret; } int iostat_prepare(struct evlist *evlist, struct perf_stat_config *config) { if (evlist->core.nr_entries > 0) { pr_warning("The -e and -M options are not supported." "All chosen events/metrics will be dropped\n"); evlist__delete(evlist); evlist = evlist__new(); if (!evlist) return -ENOMEM; } config->metric_only = true; config->aggr_mode = AGGR_GLOBAL; return iostat_event_group(evlist, root_ports); } int iostat_parse(const struct option *opt, const char *str, int unset __maybe_unused) { int ret; struct perf_stat_config *config = (struct perf_stat_config *)opt->data; ret = iio_root_ports_scan(&root_ports); if (!ret) { config->iostat_run = true; if (!str) iostat_mode = IOSTAT_RUN; else if (!strcmp(str, "list")) iostat_mode = IOSTAT_LIST; else { iostat_mode = IOSTAT_RUN; ret = iio_root_ports_list_filter(&root_ports, str); } } return ret; } void iostat_list(struct evlist *evlist, struct perf_stat_config *config) { struct evsel *evsel; struct iio_root_port *rp = NULL; evlist__for_each_entry(evlist, evsel) { if (rp != evsel->priv) { rp = evsel->priv; iio_root_port_show(config->output, rp); } } } void iostat_release(struct evlist *evlist) { struct evsel *evsel; struct iio_root_port *rp = NULL; evlist__for_each_entry(evlist, evsel) { if (rp != evsel->priv) { rp = evsel->priv; zfree(&evsel->priv); } } } void iostat_prefix(struct evlist *evlist, struct perf_stat_config *config, char *prefix, struct timespec *ts) { struct iio_root_port *rp = evlist->selected->priv; if (rp) { if (ts) sprintf(prefix, "%6lu.%09lu%s%04x:%02x%s", ts->tv_sec, ts->tv_nsec, config->csv_sep, rp->domain, rp->bus, config->csv_sep); else sprintf(prefix, "%04x:%02x%s", rp->domain, rp->bus, config->csv_sep); } } void iostat_print_header_prefix(struct perf_stat_config *config) { if (config->csv_output) fputs("port,", config->output); else if (config->interval) fprintf(config->output, "# time port "); else fprintf(config->output, " port "); } void iostat_print_metric(struct perf_stat_config *config, struct evsel *evsel, struct perf_stat_output_ctx *out) { double iostat_value = 0; u64 prev_count_val = 0; const char *iostat_metric = iostat_metric_by_idx(evsel->core.idx); u8 die = ((struct iio_root_port *)evsel->priv)->die; struct perf_counts_values *count = perf_counts(evsel->counts, die, 0); if (count && count->run && count->ena) { if (evsel->prev_raw_counts && !out->force_header) { struct perf_counts_values *prev_count = perf_counts(evsel->prev_raw_counts, die, 0); prev_count_val = prev_count->val; prev_count->val = count->val; } iostat_value = (count->val - prev_count_val) / ((double) count->run / count->ena); } out->print_metric(config, out->ctx, NULL, "%8.0f", iostat_metric, iostat_value / (256 * 1024)); } void iostat_print_counters(struct evlist *evlist, struct perf_stat_config *config, struct timespec *ts, char *prefix, iostat_print_counter_t print_cnt_cb, void *arg) { void *perf_device = NULL; struct evsel *counter = evlist__first(evlist); evlist__set_selected(evlist, counter); iostat_prefix(evlist, config, prefix, ts); fprintf(config->output, "%s", prefix); evlist__for_each_entry(evlist, counter) { perf_device = evlist->selected->priv; if (perf_device && perf_device != counter->priv) { evlist__set_selected(evlist, counter); iostat_prefix(evlist, config, prefix, ts); fprintf(config->output, "\n%s", prefix); } print_cnt_cb(config, counter, arg); } fputc('\n', config->output); }