| 1 | // SPDX-License-Identifier: GPL-2.0 |
| 2 | /* |
| 3 | * perf iostat |
| 4 | * |
| 5 | * Copyright (C) 2020, Intel Corporation |
| 6 | * |
| 7 | * Authors: Alexander Antonov <alexander.antonov@linux.intel.com> |
| 8 | */ |
| 9 | |
| 10 | #include <api/fs/fs.h> |
| 11 | #include <linux/kernel.h> |
| 12 | #include <linux/err.h> |
| 13 | #include <linux/zalloc.h> |
| 14 | #include <limits.h> |
| 15 | #include <stdio.h> |
| 16 | #include <string.h> |
| 17 | #include <errno.h> |
| 18 | #include <sys/types.h> |
| 19 | #include <sys/stat.h> |
| 20 | #include <fcntl.h> |
| 21 | #include <dirent.h> |
| 22 | #include <unistd.h> |
| 23 | #include <stdlib.h> |
| 24 | #include <regex.h> |
| 25 | #include "util/cpumap.h" |
| 26 | #include "util/debug.h" |
| 27 | #include "util/iostat.h" |
| 28 | #include "util/counts.h" |
| 29 | #include "path.h" |
| 30 | |
| 31 | #ifndef MAX_PATH |
| 32 | #define MAX_PATH 1024 |
| 33 | #endif |
| 34 | |
| 35 | #define UNCORE_IIO_PMU_PATH "bus/event_source/devices/uncore_iio_%d" |
| 36 | #define SYSFS_UNCORE_PMU_PATH "%s/"UNCORE_IIO_PMU_PATH |
| 37 | #define PLATFORM_MAPPING_PATH UNCORE_IIO_PMU_PATH"/die%d" |
| 38 | |
| 39 | /* |
| 40 | * Each metric requiries one IIO event which increments at every 4B transfer |
| 41 | * in corresponding direction. The formulas to compute metrics are generic: |
| 42 | * #EventCount * 4B / (1024 * 1024) |
| 43 | */ |
| 44 | static const char * const iostat_metrics[] = { |
| 45 | "Inbound Read(MB)" , |
| 46 | "Inbound Write(MB)" , |
| 47 | "Outbound Read(MB)" , |
| 48 | "Outbound Write(MB)" , |
| 49 | }; |
| 50 | |
| 51 | static inline int iostat_metrics_count(void) |
| 52 | { |
| 53 | return sizeof(iostat_metrics) / sizeof(char *); |
| 54 | } |
| 55 | |
| 56 | static const char *iostat_metric_by_idx(int idx) |
| 57 | { |
| 58 | return *(iostat_metrics + idx % iostat_metrics_count()); |
| 59 | } |
| 60 | |
| 61 | struct iio_root_port { |
| 62 | u32 domain; |
| 63 | u8 bus; |
| 64 | u8 die; |
| 65 | u8 pmu_idx; |
| 66 | int idx; |
| 67 | }; |
| 68 | |
| 69 | struct iio_root_ports_list { |
| 70 | struct iio_root_port **rps; |
| 71 | int nr_entries; |
| 72 | }; |
| 73 | |
| 74 | static struct iio_root_ports_list *root_ports; |
| 75 | |
| 76 | static void iio_root_port_show(FILE *output, |
| 77 | const struct iio_root_port * const rp) |
| 78 | { |
| 79 | if (output && rp) |
| 80 | fprintf(output, "S%d-uncore_iio_%d<%04x:%02x>\n" , |
| 81 | rp->die, rp->pmu_idx, rp->domain, rp->bus); |
| 82 | } |
| 83 | |
| 84 | static struct iio_root_port *iio_root_port_new(u32 domain, u8 bus, |
| 85 | u8 die, u8 pmu_idx) |
| 86 | { |
| 87 | struct iio_root_port *p = calloc(1, sizeof(*p)); |
| 88 | |
| 89 | if (p) { |
| 90 | p->domain = domain; |
| 91 | p->bus = bus; |
| 92 | p->die = die; |
| 93 | p->pmu_idx = pmu_idx; |
| 94 | } |
| 95 | return p; |
| 96 | } |
| 97 | |
| 98 | static void iio_root_ports_list_free(struct iio_root_ports_list *list) |
| 99 | { |
| 100 | int idx; |
| 101 | |
| 102 | if (list) { |
| 103 | for (idx = 0; idx < list->nr_entries; idx++) |
| 104 | zfree(&list->rps[idx]); |
| 105 | zfree(&list->rps); |
| 106 | free(list); |
| 107 | } |
| 108 | } |
| 109 | |
| 110 | static struct iio_root_port *iio_root_port_find_by_notation( |
| 111 | const struct iio_root_ports_list * const list, u32 domain, u8 bus) |
| 112 | { |
| 113 | int idx; |
| 114 | struct iio_root_port *rp; |
| 115 | |
| 116 | if (list) { |
| 117 | for (idx = 0; idx < list->nr_entries; idx++) { |
| 118 | rp = list->rps[idx]; |
| 119 | if (rp && rp->domain == domain && rp->bus == bus) |
| 120 | return rp; |
| 121 | } |
| 122 | } |
| 123 | return NULL; |
| 124 | } |
| 125 | |
| 126 | static int iio_root_ports_list_insert(struct iio_root_ports_list *list, |
| 127 | struct iio_root_port * const rp) |
| 128 | { |
| 129 | struct iio_root_port **tmp_buf; |
| 130 | |
| 131 | if (list && rp) { |
| 132 | rp->idx = list->nr_entries++; |
| 133 | tmp_buf = realloc(list->rps, |
| 134 | list->nr_entries * sizeof(*list->rps)); |
| 135 | if (!tmp_buf) { |
| 136 | pr_err("Failed to realloc memory\n" ); |
| 137 | return -ENOMEM; |
| 138 | } |
| 139 | tmp_buf[rp->idx] = rp; |
| 140 | list->rps = tmp_buf; |
| 141 | } |
| 142 | return 0; |
| 143 | } |
| 144 | |
| 145 | static int iio_mapping(u8 pmu_idx, struct iio_root_ports_list * const list) |
| 146 | { |
| 147 | char *buf; |
| 148 | char path[MAX_PATH]; |
| 149 | u32 domain; |
| 150 | u8 bus; |
| 151 | struct iio_root_port *rp; |
| 152 | size_t size; |
| 153 | int ret; |
| 154 | |
| 155 | for (int die = 0; die < cpu__max_node(); die++) { |
| 156 | scnprintf(buf: path, MAX_PATH, PLATFORM_MAPPING_PATH, pmu_idx, die); |
| 157 | if (sysfs__read_str(path, &buf, &size) < 0) { |
| 158 | if (pmu_idx) |
| 159 | goto out; |
| 160 | pr_err("Mode iostat is not supported\n" ); |
| 161 | return -1; |
| 162 | } |
| 163 | ret = sscanf(buf, "%04x:%02hhx" , &domain, &bus); |
| 164 | free(buf); |
| 165 | if (ret != 2) { |
| 166 | pr_err("Invalid mapping data: iio_%d; die%d\n" , |
| 167 | pmu_idx, die); |
| 168 | return -1; |
| 169 | } |
| 170 | rp = iio_root_port_new(domain, bus, die, pmu_idx); |
| 171 | if (!rp || iio_root_ports_list_insert(list, rp)) { |
| 172 | free(rp); |
| 173 | return -ENOMEM; |
| 174 | } |
| 175 | } |
| 176 | out: |
| 177 | return 0; |
| 178 | } |
| 179 | |
| 180 | static u8 iio_pmu_count(void) |
| 181 | { |
| 182 | u8 pmu_idx = 0; |
| 183 | char path[MAX_PATH]; |
| 184 | const char *sysfs = sysfs__mountpoint(); |
| 185 | |
| 186 | if (sysfs) { |
| 187 | for (;; pmu_idx++) { |
| 188 | snprintf(buf: path, size: sizeof(path), SYSFS_UNCORE_PMU_PATH, |
| 189 | sysfs, pmu_idx); |
| 190 | if (access(path, F_OK) != 0) |
| 191 | break; |
| 192 | } |
| 193 | } |
| 194 | return pmu_idx; |
| 195 | } |
| 196 | |
| 197 | static int iio_root_ports_scan(struct iio_root_ports_list **list) |
| 198 | { |
| 199 | int ret = -ENOMEM; |
| 200 | struct iio_root_ports_list *tmp_list; |
| 201 | u8 pmu_count = iio_pmu_count(); |
| 202 | |
| 203 | if (!pmu_count) { |
| 204 | pr_err("Unsupported uncore pmu configuration\n" ); |
| 205 | return -1; |
| 206 | } |
| 207 | |
| 208 | tmp_list = calloc(1, sizeof(*tmp_list)); |
| 209 | if (!tmp_list) |
| 210 | goto err; |
| 211 | |
| 212 | for (u8 pmu_idx = 0; pmu_idx < pmu_count; pmu_idx++) { |
| 213 | ret = iio_mapping(pmu_idx, list: tmp_list); |
| 214 | if (ret) |
| 215 | break; |
| 216 | } |
| 217 | err: |
| 218 | if (!ret) |
| 219 | *list = tmp_list; |
| 220 | else |
| 221 | iio_root_ports_list_free(list: tmp_list); |
| 222 | |
| 223 | return ret; |
| 224 | } |
| 225 | |
| 226 | static int iio_root_port_parse_str(u32 *domain, u8 *bus, char *str) |
| 227 | { |
| 228 | int ret; |
| 229 | regex_t regex; |
| 230 | /* |
| 231 | * Expected format domain:bus: |
| 232 | * Valid domain range [0:ffff] |
| 233 | * Valid bus range [0:ff] |
| 234 | * Example: 0000:af, 0:3d, 01:7 |
| 235 | */ |
| 236 | regcomp(®ex, "^([a-f0-9A-F]{1,}):([a-f0-9A-F]{1,2})" , REG_EXTENDED); |
| 237 | ret = regexec(®ex, str, 0, NULL, 0); |
| 238 | if (ret || sscanf(str, "%08x:%02hhx" , domain, bus) != 2) |
| 239 | pr_warning("Unrecognized root port format: %s\n" |
| 240 | "Please use the following format:\n" |
| 241 | "\t [domain]:[bus]\n" |
| 242 | "\t for example: 0000:3d\n" , str); |
| 243 | |
| 244 | regfree(®ex); |
| 245 | return ret; |
| 246 | } |
| 247 | |
| 248 | static int iio_root_ports_list_filter(struct iio_root_ports_list **list, |
| 249 | const char *filter) |
| 250 | { |
| 251 | char *tok, *tmp, *filter_copy = NULL; |
| 252 | struct iio_root_port *rp; |
| 253 | u32 domain; |
| 254 | u8 bus; |
| 255 | int ret = -ENOMEM; |
| 256 | struct iio_root_ports_list *tmp_list = calloc(1, sizeof(*tmp_list)); |
| 257 | |
| 258 | if (!tmp_list) |
| 259 | goto err; |
| 260 | |
| 261 | filter_copy = strdup(filter); |
| 262 | if (!filter_copy) |
| 263 | goto err; |
| 264 | |
| 265 | for (tok = strtok_r(filter_copy, "," , &tmp); tok; |
| 266 | tok = strtok_r(NULL, "," , &tmp)) { |
| 267 | if (!iio_root_port_parse_str(domain: &domain, bus: &bus, str: tok)) { |
| 268 | rp = iio_root_port_find_by_notation(list: *list, domain, bus); |
| 269 | if (rp) { |
| 270 | (*list)->rps[rp->idx] = NULL; |
| 271 | ret = iio_root_ports_list_insert(list: tmp_list, rp); |
| 272 | if (ret) { |
| 273 | free(rp); |
| 274 | goto err; |
| 275 | } |
| 276 | } else if (!iio_root_port_find_by_notation(list: tmp_list, |
| 277 | domain, bus)) |
| 278 | pr_warning("Root port %04x:%02x were not found\n" , |
| 279 | domain, bus); |
| 280 | } |
| 281 | } |
| 282 | |
| 283 | if (tmp_list->nr_entries == 0) { |
| 284 | pr_err("Requested root ports were not found\n" ); |
| 285 | ret = -EINVAL; |
| 286 | } |
| 287 | err: |
| 288 | iio_root_ports_list_free(list: *list); |
| 289 | if (ret) |
| 290 | iio_root_ports_list_free(list: tmp_list); |
| 291 | else |
| 292 | *list = tmp_list; |
| 293 | |
| 294 | free(filter_copy); |
| 295 | return ret; |
| 296 | } |
| 297 | |
| 298 | static int iostat_event_group(struct evlist *evl, |
| 299 | struct iio_root_ports_list *list) |
| 300 | { |
| 301 | int ret; |
| 302 | int idx; |
| 303 | const char *iostat_cmd_template = |
| 304 | "{uncore_iio_%x/event=0x83,umask=0x04,ch_mask=0xF,fc_mask=0x07/,\ |
| 305 | uncore_iio_%x/event=0x83,umask=0x01,ch_mask=0xF,fc_mask=0x07/,\ |
| 306 | uncore_iio_%x/event=0xc0,umask=0x04,ch_mask=0xF,fc_mask=0x07/,\ |
| 307 | uncore_iio_%x/event=0xc0,umask=0x01,ch_mask=0xF,fc_mask=0x07/}" ; |
| 308 | const int len_template = strlen(iostat_cmd_template) + 1; |
| 309 | struct evsel *evsel = NULL; |
| 310 | int metrics_count = iostat_metrics_count(); |
| 311 | char *iostat_cmd = calloc(len_template, 1); |
| 312 | |
| 313 | if (!iostat_cmd) |
| 314 | return -ENOMEM; |
| 315 | |
| 316 | for (idx = 0; idx < list->nr_entries; idx++) { |
| 317 | sprintf(buf: iostat_cmd, fmt: iostat_cmd_template, |
| 318 | list->rps[idx]->pmu_idx, list->rps[idx]->pmu_idx, |
| 319 | list->rps[idx]->pmu_idx, list->rps[idx]->pmu_idx); |
| 320 | ret = parse_event(evl, iostat_cmd); |
| 321 | if (ret) |
| 322 | goto err; |
| 323 | } |
| 324 | |
| 325 | evlist__for_each_entry(evl, evsel) { |
| 326 | evsel->priv = list->rps[evsel->core.idx / metrics_count]; |
| 327 | } |
| 328 | list->nr_entries = 0; |
| 329 | err: |
| 330 | iio_root_ports_list_free(list); |
| 331 | free(iostat_cmd); |
| 332 | return ret; |
| 333 | } |
| 334 | |
| 335 | int iostat_prepare(struct evlist *evlist, struct perf_stat_config *config) |
| 336 | { |
| 337 | if (evlist->core.nr_entries > 0) { |
| 338 | pr_warning("The -e and -M options are not supported." |
| 339 | "All chosen events/metrics will be dropped\n" ); |
| 340 | evlist__delete(evlist); |
| 341 | evlist = evlist__new(); |
| 342 | if (!evlist) |
| 343 | return -ENOMEM; |
| 344 | } |
| 345 | |
| 346 | config->metric_only = true; |
| 347 | config->aggr_mode = AGGR_GLOBAL; |
| 348 | |
| 349 | return iostat_event_group(evl: evlist, list: root_ports); |
| 350 | } |
| 351 | |
| 352 | int iostat_parse(const struct option *opt, const char *str, |
| 353 | int unset __maybe_unused) |
| 354 | { |
| 355 | int ret; |
| 356 | struct perf_stat_config *config = (struct perf_stat_config *)opt->data; |
| 357 | |
| 358 | ret = iio_root_ports_scan(list: &root_ports); |
| 359 | if (!ret) { |
| 360 | config->iostat_run = true; |
| 361 | if (!str) |
| 362 | iostat_mode = IOSTAT_RUN; |
| 363 | else if (!strcmp(str, "list" )) |
| 364 | iostat_mode = IOSTAT_LIST; |
| 365 | else { |
| 366 | iostat_mode = IOSTAT_RUN; |
| 367 | ret = iio_root_ports_list_filter(list: &root_ports, filter: str); |
| 368 | } |
| 369 | } |
| 370 | return ret; |
| 371 | } |
| 372 | |
| 373 | void iostat_list(struct evlist *evlist, struct perf_stat_config *config) |
| 374 | { |
| 375 | struct evsel *evsel; |
| 376 | struct iio_root_port *rp = NULL; |
| 377 | |
| 378 | evlist__for_each_entry(evlist, evsel) { |
| 379 | if (rp != evsel->priv) { |
| 380 | rp = evsel->priv; |
| 381 | iio_root_port_show(config->output, rp); |
| 382 | } |
| 383 | } |
| 384 | } |
| 385 | |
| 386 | void iostat_release(struct evlist *evlist) |
| 387 | { |
| 388 | struct evsel *evsel; |
| 389 | struct iio_root_port *rp = NULL; |
| 390 | |
| 391 | evlist__for_each_entry(evlist, evsel) { |
| 392 | if (rp != evsel->priv) { |
| 393 | rp = evsel->priv; |
| 394 | zfree(&evsel->priv); |
| 395 | } |
| 396 | } |
| 397 | } |
| 398 | |
| 399 | void iostat_prefix(struct evlist *evlist, |
| 400 | struct perf_stat_config *config, |
| 401 | char *prefix, struct timespec *ts) |
| 402 | { |
| 403 | struct iio_root_port *rp = evlist->selected->priv; |
| 404 | |
| 405 | if (rp) { |
| 406 | /* |
| 407 | * TODO: This is the incorrect format in JSON mode. |
| 408 | * See prepare_timestamp() |
| 409 | */ |
| 410 | if (ts) |
| 411 | sprintf(buf: prefix, fmt: "%6lu.%09lu%s%04x:%02x%s" , |
| 412 | ts->tv_sec, ts->tv_nsec, |
| 413 | config->csv_sep, rp->domain, rp->bus, |
| 414 | config->csv_sep); |
| 415 | else |
| 416 | sprintf(buf: prefix, fmt: "%04x:%02x%s" , rp->domain, rp->bus, |
| 417 | config->csv_sep); |
| 418 | } |
| 419 | } |
| 420 | |
| 421 | void (struct perf_stat_config *config) |
| 422 | { |
| 423 | if (config->csv_output) |
| 424 | fputs("port," , config->output); |
| 425 | else if (config->interval) |
| 426 | fprintf(config->output, "# time port " ); |
| 427 | else |
| 428 | fprintf(config->output, " port " ); |
| 429 | } |
| 430 | |
| 431 | void iostat_print_metric(struct perf_stat_config *config, struct evsel *evsel, |
| 432 | struct perf_stat_output_ctx *out) |
| 433 | { |
| 434 | double iostat_value = 0; |
| 435 | u64 prev_count_val = 0; |
| 436 | const char *iostat_metric = iostat_metric_by_idx(idx: evsel->core.idx); |
| 437 | u8 die = ((struct iio_root_port *)evsel->priv)->die; |
| 438 | struct perf_counts_values *count = perf_counts(evsel->counts, die, 0); |
| 439 | |
| 440 | if (count && count->run && count->ena) { |
| 441 | if (evsel->prev_raw_counts && !out->force_header) { |
| 442 | struct perf_counts_values *prev_count = |
| 443 | perf_counts(evsel->prev_raw_counts, die, 0); |
| 444 | |
| 445 | prev_count_val = prev_count->val; |
| 446 | prev_count->val = count->val; |
| 447 | } |
| 448 | iostat_value = (count->val - prev_count_val) / |
| 449 | ((double) count->run / count->ena); |
| 450 | } |
| 451 | out->print_metric(config, out->ctx, METRIC_THRESHOLD_UNKNOWN, "%8.0f" , iostat_metric, |
| 452 | iostat_value / (256 * 1024)); |
| 453 | } |
| 454 | |
| 455 | void iostat_print_counters(struct evlist *evlist, |
| 456 | struct perf_stat_config *config, struct timespec *ts, |
| 457 | char *prefix, iostat_print_counter_t print_cnt_cb, void *arg) |
| 458 | { |
| 459 | void *perf_device = NULL; |
| 460 | struct evsel *counter = evlist__first(evlist); |
| 461 | |
| 462 | evlist__set_selected(evlist, counter); |
| 463 | iostat_prefix(evlist, config, prefix, ts); |
| 464 | fprintf(config->output, "%s" , prefix); |
| 465 | evlist__for_each_entry(evlist, counter) { |
| 466 | perf_device = evlist->selected->priv; |
| 467 | if (perf_device && perf_device != counter->priv) { |
| 468 | evlist__set_selected(evlist, counter); |
| 469 | iostat_prefix(evlist, config, prefix, ts); |
| 470 | fprintf(config->output, "\n%s" , prefix); |
| 471 | } |
| 472 | print_cnt_cb(config, counter, arg); |
| 473 | } |
| 474 | fputc('\n', config->output); |
| 475 | } |
| 476 | |