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 */
44static const char * const iostat_metrics[] = {
45	"Inbound Read(MB)",
46	"Inbound Write(MB)",
47	"Outbound Read(MB)",
48	"Outbound Write(MB)",
49};
50
51static inline int iostat_metrics_count(void)
52{
53	return sizeof(iostat_metrics) / sizeof(char *);
54}
55
56static const char *iostat_metric_by_idx(int idx)
57{
58	return *(iostat_metrics + idx % iostat_metrics_count());
59}
60
61struct iio_root_port {
62	u32 domain;
63	u8 bus;
64	u8 die;
65	u8 pmu_idx;
66	int idx;
67};
68
69struct iio_root_ports_list {
70	struct iio_root_port **rps;
71	int nr_entries;
72};
73
74static struct iio_root_ports_list *root_ports;
75
76static 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
84static 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
98static 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
110static 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
126static 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
145static 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	}
176out:
177	return 0;
178}
179
180static 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
197static 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	}
217err:
218	if (!ret)
219		*list = tmp_list;
220	else
221		iio_root_ports_list_free(tmp_list);
222
223	return ret;
224}
225
226static 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(&regex, "^([a-f0-9A-F]{1,}):([a-f0-9A-F]{1,2})", REG_EXTENDED);
237	ret = regexec(&regex, 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(&regex);
245	return ret;
246}
247
248static 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	}
287err:
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
298static 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;
329err:
330	iio_root_ports_list_free(list);
331	free(iostat_cmd);
332	return ret;
333}
334
335int 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
352int 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
373void 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
386void 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
399void 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
417void 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
427void 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
451void 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