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