xref: /openbmc/linux/tools/perf/util/metricgroup.c (revision e7253313)
1 // SPDX-License-Identifier: GPL-2.0-only
2 /*
3  * Copyright (c) 2017, Intel Corporation.
4  */
5 
6 /* Manage metrics and groups of metrics from JSON files */
7 
8 #include "metricgroup.h"
9 #include "debug.h"
10 #include "evlist.h"
11 #include "evsel.h"
12 #include "strbuf.h"
13 #include "pmu.h"
14 #include "expr.h"
15 #include "rblist.h"
16 #include <string.h>
17 #include <errno.h>
18 #include "pmu-events/pmu-events.h"
19 #include "strlist.h"
20 #include <assert.h>
21 #include <linux/ctype.h>
22 #include <linux/string.h>
23 #include <linux/zalloc.h>
24 #include <subcmd/parse-options.h>
25 
26 struct metric_event *metricgroup__lookup(struct rblist *metric_events,
27 					 struct evsel *evsel,
28 					 bool create)
29 {
30 	struct rb_node *nd;
31 	struct metric_event me = {
32 		.evsel = evsel
33 	};
34 
35 	if (!metric_events)
36 		return NULL;
37 
38 	nd = rblist__find(metric_events, &me);
39 	if (nd)
40 		return container_of(nd, struct metric_event, nd);
41 	if (create) {
42 		rblist__add_node(metric_events, &me);
43 		nd = rblist__find(metric_events, &me);
44 		if (nd)
45 			return container_of(nd, struct metric_event, nd);
46 	}
47 	return NULL;
48 }
49 
50 static int metric_event_cmp(struct rb_node *rb_node, const void *entry)
51 {
52 	struct metric_event *a = container_of(rb_node,
53 					      struct metric_event,
54 					      nd);
55 	const struct metric_event *b = entry;
56 
57 	if (a->evsel == b->evsel)
58 		return 0;
59 	if ((char *)a->evsel < (char *)b->evsel)
60 		return -1;
61 	return +1;
62 }
63 
64 static struct rb_node *metric_event_new(struct rblist *rblist __maybe_unused,
65 					const void *entry)
66 {
67 	struct metric_event *me = malloc(sizeof(struct metric_event));
68 
69 	if (!me)
70 		return NULL;
71 	memcpy(me, entry, sizeof(struct metric_event));
72 	me->evsel = ((struct metric_event *)entry)->evsel;
73 	INIT_LIST_HEAD(&me->head);
74 	return &me->nd;
75 }
76 
77 static void metricgroup__rblist_init(struct rblist *metric_events)
78 {
79 	rblist__init(metric_events);
80 	metric_events->node_cmp = metric_event_cmp;
81 	metric_events->node_new = metric_event_new;
82 }
83 
84 struct egroup {
85 	struct list_head nd;
86 	int idnum;
87 	const char **ids;
88 	const char *metric_name;
89 	const char *metric_expr;
90 	const char *metric_unit;
91 };
92 
93 static struct evsel *find_evsel_group(struct evlist *perf_evlist,
94 				      const char **ids,
95 				      int idnum,
96 				      struct evsel **metric_events)
97 {
98 	struct evsel *ev;
99 	int i = 0;
100 	bool leader_found;
101 
102 	evlist__for_each_entry (perf_evlist, ev) {
103 		if (!strcmp(ev->name, ids[i])) {
104 			if (!metric_events[i])
105 				metric_events[i] = ev;
106 			i++;
107 			if (i == idnum)
108 				break;
109 		} else {
110 			if (i + 1 == idnum) {
111 				/* Discard the whole match and start again */
112 				i = 0;
113 				memset(metric_events, 0,
114 				       sizeof(struct evsel *) * idnum);
115 				continue;
116 			}
117 
118 			if (!strcmp(ev->name, ids[i]))
119 				metric_events[i] = ev;
120 			else {
121 				/* Discard the whole match and start again */
122 				i = 0;
123 				memset(metric_events, 0,
124 				       sizeof(struct evsel *) * idnum);
125 				continue;
126 			}
127 		}
128 	}
129 
130 	if (i != idnum) {
131 		/* Not whole match */
132 		return NULL;
133 	}
134 
135 	metric_events[idnum] = NULL;
136 
137 	for (i = 0; i < idnum; i++) {
138 		leader_found = false;
139 		evlist__for_each_entry(perf_evlist, ev) {
140 			if (!leader_found && (ev == metric_events[i]))
141 				leader_found = true;
142 
143 			if (leader_found &&
144 			    !strcmp(ev->name, metric_events[i]->name)) {
145 				ev->metric_leader = metric_events[i];
146 			}
147 		}
148 	}
149 
150 	return metric_events[0];
151 }
152 
153 static int metricgroup__setup_events(struct list_head *groups,
154 				     struct evlist *perf_evlist,
155 				     struct rblist *metric_events_list)
156 {
157 	struct metric_event *me;
158 	struct metric_expr *expr;
159 	int i = 0;
160 	int ret = 0;
161 	struct egroup *eg;
162 	struct evsel *evsel;
163 
164 	list_for_each_entry (eg, groups, nd) {
165 		struct evsel **metric_events;
166 
167 		metric_events = calloc(sizeof(void *), eg->idnum + 1);
168 		if (!metric_events) {
169 			ret = -ENOMEM;
170 			break;
171 		}
172 		evsel = find_evsel_group(perf_evlist, eg->ids, eg->idnum,
173 					 metric_events);
174 		if (!evsel) {
175 			pr_debug("Cannot resolve %s: %s\n",
176 					eg->metric_name, eg->metric_expr);
177 			continue;
178 		}
179 		for (i = 0; i < eg->idnum; i++)
180 			metric_events[i]->collect_stat = true;
181 		me = metricgroup__lookup(metric_events_list, evsel, true);
182 		if (!me) {
183 			ret = -ENOMEM;
184 			break;
185 		}
186 		expr = malloc(sizeof(struct metric_expr));
187 		if (!expr) {
188 			ret = -ENOMEM;
189 			break;
190 		}
191 		expr->metric_expr = eg->metric_expr;
192 		expr->metric_name = eg->metric_name;
193 		expr->metric_unit = eg->metric_unit;
194 		expr->metric_events = metric_events;
195 		list_add(&expr->nd, &me->head);
196 	}
197 	return ret;
198 }
199 
200 static bool match_metric(const char *n, const char *list)
201 {
202 	int len;
203 	char *m;
204 
205 	if (!list)
206 		return false;
207 	if (!strcmp(list, "all"))
208 		return true;
209 	if (!n)
210 		return !strcasecmp(list, "No_group");
211 	len = strlen(list);
212 	m = strcasestr(n, list);
213 	if (!m)
214 		return false;
215 	if ((m == n || m[-1] == ';' || m[-1] == ' ') &&
216 	    (m[len] == 0 || m[len] == ';'))
217 		return true;
218 	return false;
219 }
220 
221 struct mep {
222 	struct rb_node nd;
223 	const char *name;
224 	struct strlist *metrics;
225 };
226 
227 static int mep_cmp(struct rb_node *rb_node, const void *entry)
228 {
229 	struct mep *a = container_of(rb_node, struct mep, nd);
230 	struct mep *b = (struct mep *)entry;
231 
232 	return strcmp(a->name, b->name);
233 }
234 
235 static struct rb_node *mep_new(struct rblist *rl __maybe_unused,
236 					const void *entry)
237 {
238 	struct mep *me = malloc(sizeof(struct mep));
239 
240 	if (!me)
241 		return NULL;
242 	memcpy(me, entry, sizeof(struct mep));
243 	me->name = strdup(me->name);
244 	if (!me->name)
245 		goto out_me;
246 	me->metrics = strlist__new(NULL, NULL);
247 	if (!me->metrics)
248 		goto out_name;
249 	return &me->nd;
250 out_name:
251 	zfree(&me->name);
252 out_me:
253 	free(me);
254 	return NULL;
255 }
256 
257 static struct mep *mep_lookup(struct rblist *groups, const char *name)
258 {
259 	struct rb_node *nd;
260 	struct mep me = {
261 		.name = name
262 	};
263 	nd = rblist__find(groups, &me);
264 	if (nd)
265 		return container_of(nd, struct mep, nd);
266 	rblist__add_node(groups, &me);
267 	nd = rblist__find(groups, &me);
268 	if (nd)
269 		return container_of(nd, struct mep, nd);
270 	return NULL;
271 }
272 
273 static void mep_delete(struct rblist *rl __maybe_unused,
274 		       struct rb_node *nd)
275 {
276 	struct mep *me = container_of(nd, struct mep, nd);
277 
278 	strlist__delete(me->metrics);
279 	zfree(&me->name);
280 	free(me);
281 }
282 
283 static void metricgroup__print_strlist(struct strlist *metrics, bool raw)
284 {
285 	struct str_node *sn;
286 	int n = 0;
287 
288 	strlist__for_each_entry (sn, metrics) {
289 		if (raw)
290 			printf("%s%s", n > 0 ? " " : "", sn->s);
291 		else
292 			printf("  %s\n", sn->s);
293 		n++;
294 	}
295 	if (raw)
296 		putchar('\n');
297 }
298 
299 void metricgroup__print(bool metrics, bool metricgroups, char *filter,
300 			bool raw, bool details)
301 {
302 	struct pmu_events_map *map = perf_pmu__find_map(NULL);
303 	struct pmu_event *pe;
304 	int i;
305 	struct rblist groups;
306 	struct rb_node *node, *next;
307 	struct strlist *metriclist = NULL;
308 
309 	if (!map)
310 		return;
311 
312 	if (!metricgroups) {
313 		metriclist = strlist__new(NULL, NULL);
314 		if (!metriclist)
315 			return;
316 	}
317 
318 	rblist__init(&groups);
319 	groups.node_new = mep_new;
320 	groups.node_cmp = mep_cmp;
321 	groups.node_delete = mep_delete;
322 	for (i = 0; ; i++) {
323 		const char *g;
324 		pe = &map->table[i];
325 
326 		if (!pe->name && !pe->metric_group && !pe->metric_name)
327 			break;
328 		if (!pe->metric_expr)
329 			continue;
330 		g = pe->metric_group;
331 		if (!g && pe->metric_name) {
332 			if (pe->name)
333 				continue;
334 			g = "No_group";
335 		}
336 		if (g) {
337 			char *omg;
338 			char *mg = strdup(g);
339 
340 			if (!mg)
341 				return;
342 			omg = mg;
343 			while ((g = strsep(&mg, ";")) != NULL) {
344 				struct mep *me;
345 				char *s;
346 
347 				g = skip_spaces(g);
348 				if (*g == 0)
349 					g = "No_group";
350 				if (filter && !strstr(g, filter))
351 					continue;
352 				if (raw)
353 					s = (char *)pe->metric_name;
354 				else {
355 					if (asprintf(&s, "%s\n%*s%s]",
356 						     pe->metric_name, 8, "[", pe->desc) < 0)
357 						return;
358 
359 					if (details) {
360 						if (asprintf(&s, "%s\n%*s%s]",
361 							     s, 8, "[", pe->metric_expr) < 0)
362 							return;
363 					}
364 				}
365 
366 				if (!s)
367 					continue;
368 
369 				if (!metricgroups) {
370 					strlist__add(metriclist, s);
371 				} else {
372 					me = mep_lookup(&groups, g);
373 					if (!me)
374 						continue;
375 					strlist__add(me->metrics, s);
376 				}
377 			}
378 			free(omg);
379 		}
380 	}
381 
382 	if (metricgroups && !raw)
383 		printf("\nMetric Groups:\n\n");
384 	else if (metrics && !raw)
385 		printf("\nMetrics:\n\n");
386 
387 	for (node = rb_first_cached(&groups.entries); node; node = next) {
388 		struct mep *me = container_of(node, struct mep, nd);
389 
390 		if (metricgroups)
391 			printf("%s%s%s", me->name, metrics && !raw ? ":" : "", raw ? " " : "\n");
392 		if (metrics)
393 			metricgroup__print_strlist(me->metrics, raw);
394 		next = rb_next(node);
395 		rblist__remove_node(&groups, node);
396 	}
397 	if (!metricgroups)
398 		metricgroup__print_strlist(metriclist, raw);
399 	strlist__delete(metriclist);
400 }
401 
402 static int metricgroup__add_metric(const char *metric, struct strbuf *events,
403 				   struct list_head *group_list)
404 {
405 	struct pmu_events_map *map = perf_pmu__find_map(NULL);
406 	struct pmu_event *pe;
407 	int ret = -EINVAL;
408 	int i, j;
409 
410 	if (!map)
411 		return 0;
412 
413 	for (i = 0; ; i++) {
414 		pe = &map->table[i];
415 
416 		if (!pe->name && !pe->metric_group && !pe->metric_name)
417 			break;
418 		if (!pe->metric_expr)
419 			continue;
420 		if (match_metric(pe->metric_group, metric) ||
421 		    match_metric(pe->metric_name, metric)) {
422 			const char **ids;
423 			int idnum;
424 			struct egroup *eg;
425 			bool no_group = false;
426 
427 			pr_debug("metric expr %s for %s\n", pe->metric_expr, pe->metric_name);
428 
429 			if (expr__find_other(pe->metric_expr,
430 					     NULL, &ids, &idnum) < 0)
431 				continue;
432 			if (events->len > 0)
433 				strbuf_addf(events, ",");
434 			for (j = 0; j < idnum; j++) {
435 				pr_debug("found event %s\n", ids[j]);
436 				/*
437 				 * Duration time maps to a software event and can make
438 				 * groups not count. Always use it outside a
439 				 * group.
440 				 */
441 				if (!strcmp(ids[j], "duration_time")) {
442 					if (j > 0)
443 						strbuf_addf(events, "}:W,");
444 					strbuf_addf(events, "duration_time");
445 					no_group = true;
446 					continue;
447 				}
448 				strbuf_addf(events, "%s%s",
449 					j == 0 || no_group ? "{" : ",",
450 					ids[j]);
451 				no_group = false;
452 			}
453 			if (!no_group)
454 				strbuf_addf(events, "}:W");
455 
456 			eg = malloc(sizeof(struct egroup));
457 			if (!eg) {
458 				ret = -ENOMEM;
459 				break;
460 			}
461 			eg->ids = ids;
462 			eg->idnum = idnum;
463 			eg->metric_name = pe->metric_name;
464 			eg->metric_expr = pe->metric_expr;
465 			eg->metric_unit = pe->unit;
466 			list_add_tail(&eg->nd, group_list);
467 			ret = 0;
468 		}
469 	}
470 	return ret;
471 }
472 
473 static int metricgroup__add_metric_list(const char *list, struct strbuf *events,
474 				        struct list_head *group_list)
475 {
476 	char *llist, *nlist, *p;
477 	int ret = -EINVAL;
478 
479 	nlist = strdup(list);
480 	if (!nlist)
481 		return -ENOMEM;
482 	llist = nlist;
483 
484 	strbuf_init(events, 100);
485 	strbuf_addf(events, "%s", "");
486 
487 	while ((p = strsep(&llist, ",")) != NULL) {
488 		ret = metricgroup__add_metric(p, events, group_list);
489 		if (ret == -EINVAL) {
490 			fprintf(stderr, "Cannot find metric or group `%s'\n",
491 					p);
492 			break;
493 		}
494 	}
495 	free(nlist);
496 	return ret;
497 }
498 
499 static void metricgroup__free_egroups(struct list_head *group_list)
500 {
501 	struct egroup *eg, *egtmp;
502 	int i;
503 
504 	list_for_each_entry_safe (eg, egtmp, group_list, nd) {
505 		for (i = 0; i < eg->idnum; i++)
506 			zfree(&eg->ids[i]);
507 		zfree(&eg->ids);
508 		list_del_init(&eg->nd);
509 		free(eg);
510 	}
511 }
512 
513 int metricgroup__parse_groups(const struct option *opt,
514 			   const char *str,
515 			   struct rblist *metric_events)
516 {
517 	struct parse_events_error parse_error;
518 	struct evlist *perf_evlist = *(struct evlist **)opt->value;
519 	struct strbuf extra_events;
520 	LIST_HEAD(group_list);
521 	int ret;
522 
523 	if (metric_events->nr_entries == 0)
524 		metricgroup__rblist_init(metric_events);
525 	ret = metricgroup__add_metric_list(str, &extra_events, &group_list);
526 	if (ret)
527 		return ret;
528 	pr_debug("adding %s\n", extra_events.buf);
529 	bzero(&parse_error, sizeof(parse_error));
530 	ret = parse_events(perf_evlist, extra_events.buf, &parse_error);
531 	if (ret) {
532 		parse_events_print_error(&parse_error, extra_events.buf);
533 		goto out;
534 	}
535 	strbuf_release(&extra_events);
536 	ret = metricgroup__setup_events(&group_list, perf_evlist,
537 					metric_events);
538 out:
539 	metricgroup__free_egroups(&group_list);
540 	return ret;
541 }
542 
543 bool metricgroup__has_metric(const char *metric)
544 {
545 	struct pmu_events_map *map = perf_pmu__find_map(NULL);
546 	struct pmu_event *pe;
547 	int i;
548 
549 	if (!map)
550 		return false;
551 
552 	for (i = 0; ; i++) {
553 		pe = &map->table[i];
554 
555 		if (!pe->name && !pe->metric_group && !pe->metric_name)
556 			break;
557 		if (!pe->metric_expr)
558 			continue;
559 		if (match_metric(pe->metric_name, metric))
560 			return true;
561 	}
562 	return false;
563 }
564