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 "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
iostat_metrics_count(void)51 static inline int iostat_metrics_count(void)
52 {
53 return sizeof(iostat_metrics) / sizeof(char *);
54 }
55
iostat_metric_by_idx(int idx)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
iio_root_port_show(FILE * output,const struct iio_root_port * const rp)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
iio_root_port_new(u32 domain,u8 bus,u8 die,u8 pmu_idx)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
iio_root_ports_list_free(struct iio_root_ports_list * list)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
iio_root_port_find_by_notation(const struct iio_root_ports_list * const list,u32 domain,u8 bus)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
iio_root_ports_list_insert(struct iio_root_ports_list * list,struct iio_root_port * const rp)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
iio_mapping(u8 pmu_idx,struct iio_root_ports_list * const list)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(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
iio_pmu_count(void)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(path, 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
iio_root_ports_scan(struct iio_root_ports_list ** list)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, 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(tmp_list);
222
223 return ret;
224 }
225
iio_root_port_parse_str(u32 * domain,u8 * bus,char * str)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
iio_root_ports_list_filter(struct iio_root_ports_list ** list,const char * filter)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, &bus, tok)) {
268 rp = iio_root_port_find_by_notation(*list, domain, bus);
269 if (rp) {
270 (*list)->rps[rp->idx] = NULL;
271 ret = iio_root_ports_list_insert(tmp_list, rp);
272 if (ret) {
273 free(rp);
274 goto err;
275 }
276 } else if (!iio_root_port_find_by_notation(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);
289 if (ret)
290 iio_root_ports_list_free(tmp_list);
291 else
292 *list = tmp_list;
293
294 free(filter_copy);
295 return ret;
296 }
297
iostat_event_group(struct evlist * evl,struct iio_root_ports_list * list)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(iostat_cmd, 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
iostat_prepare(struct evlist * evlist,struct perf_stat_config * config)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(evlist, root_ports);
350 }
351
iostat_parse(const struct option * opt,const char * str,int unset __maybe_unused)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(&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(&root_ports, str);
368 }
369 }
370 return ret;
371 }
372
iostat_list(struct evlist * evlist,struct perf_stat_config * config)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
iostat_release(struct evlist * evlist)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
iostat_prefix(struct evlist * evlist,struct perf_stat_config * config,char * prefix,struct timespec * ts)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 if (ts)
407 sprintf(prefix, "%6lu.%09lu%s%04x:%02x%s",
408 ts->tv_sec, ts->tv_nsec,
409 config->csv_sep, rp->domain, rp->bus,
410 config->csv_sep);
411 else
412 sprintf(prefix, "%04x:%02x%s", rp->domain, rp->bus,
413 config->csv_sep);
414 }
415 }
416
iostat_print_header_prefix(struct perf_stat_config * config)417 void iostat_print_header_prefix(struct perf_stat_config *config)
418 {
419 if (config->csv_output)
420 fputs("port,", config->output);
421 else if (config->interval)
422 fprintf(config->output, "# time port ");
423 else
424 fprintf(config->output, " port ");
425 }
426
iostat_print_metric(struct perf_stat_config * config,struct evsel * evsel,struct perf_stat_output_ctx * out)427 void iostat_print_metric(struct perf_stat_config *config, struct evsel *evsel,
428 struct perf_stat_output_ctx *out)
429 {
430 double iostat_value = 0;
431 u64 prev_count_val = 0;
432 const char *iostat_metric = iostat_metric_by_idx(evsel->core.idx);
433 u8 die = ((struct iio_root_port *)evsel->priv)->die;
434 struct perf_counts_values *count = perf_counts(evsel->counts, die, 0);
435
436 if (count && count->run && count->ena) {
437 if (evsel->prev_raw_counts && !out->force_header) {
438 struct perf_counts_values *prev_count =
439 perf_counts(evsel->prev_raw_counts, die, 0);
440
441 prev_count_val = prev_count->val;
442 prev_count->val = count->val;
443 }
444 iostat_value = (count->val - prev_count_val) /
445 ((double) count->run / count->ena);
446 }
447 out->print_metric(config, out->ctx, NULL, "%8.0f", iostat_metric,
448 iostat_value / (256 * 1024));
449 }
450
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)451 void iostat_print_counters(struct evlist *evlist,
452 struct perf_stat_config *config, struct timespec *ts,
453 char *prefix, iostat_print_counter_t print_cnt_cb, void *arg)
454 {
455 void *perf_device = NULL;
456 struct evsel *counter = evlist__first(evlist);
457
458 evlist__set_selected(evlist, counter);
459 iostat_prefix(evlist, config, prefix, ts);
460 fprintf(config->output, "%s", prefix);
461 evlist__for_each_entry(evlist, counter) {
462 perf_device = evlist->selected->priv;
463 if (perf_device && perf_device != counter->priv) {
464 evlist__set_selected(evlist, counter);
465 iostat_prefix(evlist, config, prefix, ts);
466 fprintf(config->output, "\n%s", prefix);
467 }
468 print_cnt_cb(config, counter, arg);
469 }
470 fputc('\n', config->output);
471 }
472