perf tools: Add record/stat support for toggling events

Signed-off-by: Jiri Olsa <jolsa@redhat.com>
Cc: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Corey Ashford <cjashfor@linux.vnet.ibm.com>
Cc: Frederic Weisbecker <fweisbec@gmail.com>
Cc: Ingo Molnar <mingo@elte.hu>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Arnaldo Carvalho de Melo <acme@redhat.com>
diff --git a/tools/perf/builtin-record.c b/tools/perf/builtin-record.c
index a41ac415..b3cf903 100644
--- a/tools/perf/builtin-record.c
+++ b/tools/perf/builtin-record.c
@@ -226,6 +226,13 @@
 		goto out;
 	}
 
+	if (perf_evlist__apply_toggle(evlist)) {
+		error("failed to set toggling %d (%s)\n", errno,
+			strerror(errno));
+		rc = -1;
+		goto out;
+	}
+
 	if (perf_evlist__mmap(evlist, opts->mmap_pages, false) < 0) {
 		if (errno == EPERM) {
 			pr_err("Permission error mapping pages.\n"
diff --git a/tools/perf/builtin-stat.c b/tools/perf/builtin-stat.c
index f686d5f..5d5db81 100644
--- a/tools/perf/builtin-stat.c
+++ b/tools/perf/builtin-stat.c
@@ -462,6 +462,8 @@
 	if (group)
 		perf_evlist__set_leader(evsel_list);
 
+	perf_evlist__mark_toggled(evsel_list);
+
 	list_for_each_entry(counter, &evsel_list->entries, node) {
 		if (create_perf_stat_counter(counter) < 0) {
 			/*
@@ -496,6 +498,12 @@
 		return -1;
 	}
 
+	if (perf_evlist__apply_toggle(evsel_list)) {
+		error("failed to set toggling with %d (%s)\n", errno,
+			strerror(errno));
+		return -1;
+	}
+
 	/*
 	 * Enable counters and exec the command:
 	 */
diff --git a/tools/perf/util/evlist.c b/tools/perf/util/evlist.c
index 5df4ca9..ab42be1 100644
--- a/tools/perf/util/evlist.c
+++ b/tools/perf/util/evlist.c
@@ -16,6 +16,7 @@
 #include "evsel.h"
 #include "debug.h"
 #include <unistd.h>
+#include "asm/bug.h"
 
 #include "parse-events.h"
 
@@ -706,6 +707,85 @@
 	evlist->threads = NULL;
 }
 
+static struct perf_evsel *
+perf_evlist__find_evsel_by_name(struct perf_evlist *evlist, char *name)
+{
+	struct perf_evsel *evsel;
+
+	list_for_each_entry(evsel, &evlist->entries, node)
+		if (strstr(perf_evsel__name(evsel), name))
+			return evsel;
+
+	return NULL;
+}
+
+static int apply_toggle(struct perf_evsel *evsel, struct perf_evsel *toggled,
+			int ncpus, int nthreads)
+{
+	int cpu, thread, err = 0;
+
+	for (cpu = 0; cpu < ncpus; cpu++) {
+		for (thread = 0; thread < nthreads; thread++) {
+			int fd = FD(evsel, cpu, thread);
+			u64 args[2] = {
+				FD(toggled, cpu, thread),
+				evsel->toggle_flag
+			};
+
+			err = ioctl(fd, PERF_EVENT_IOC_SET_TOGGLE, args);
+			if (err)
+				break;
+		}
+	}
+
+	return err;
+}
+
+int perf_evlist__apply_toggle(struct perf_evlist *evlist)
+{
+	struct perf_evsel *evsel;
+	int err = 0;
+	const int ncpus = cpu_map__nr(evlist->cpus),
+		  nthreads = thread_map__nr(evlist->threads);
+
+	list_for_each_entry(evsel, &evlist->entries, node) {
+		struct perf_evsel *toggled;
+
+		if (!evsel->toggle_flag)
+			continue;
+
+		toggled = perf_evlist__find_evsel_by_name(evlist,
+							  evsel->toggle_name);
+		if (WARN_ONCE(!toggled, "toggled support internal error\n"))
+			continue;
+
+		err = apply_toggle(evsel, toggled, ncpus, nthreads);
+		if (err)
+			break;
+	}
+
+	return err;
+}
+
+void perf_evlist__mark_toggled(struct perf_evlist *evlist)
+{
+	struct perf_evsel *evsel;
+
+	list_for_each_entry(evsel, &evlist->entries, node) {
+		struct perf_evsel *toggled;
+
+		if (!evsel->toggle_flag)
+			continue;
+
+		toggled = perf_evlist__find_evsel_by_name(evlist,
+							  evsel->toggle_name);
+		if (WARN_ONCE(!toggled, "toggled support internal error\n"))
+			continue;
+
+		toggled->is_toggled = true;
+	}
+}
+
 int perf_evlist__apply_filters(struct perf_evlist *evlist)
 {
 	struct perf_evsel *evsel;
@@ -714,6 +794,7 @@
 		  nthreads = thread_map__nr(evlist->threads);
 
 	list_for_each_entry(evsel, &evlist->entries, node) {
+
 		if (evsel->filter == NULL)
 			continue;
 
diff --git a/tools/perf/util/evlist.h b/tools/perf/util/evlist.h
index 841a394..e4fe688 100644
--- a/tools/perf/util/evlist.h
+++ b/tools/perf/util/evlist.h
@@ -124,6 +124,8 @@
 int perf_evlist__create_maps(struct perf_evlist *evlist,
 			     struct perf_target *target);
 void perf_evlist__delete_maps(struct perf_evlist *evlist);
+void perf_evlist__mark_toggled(struct perf_evlist *evlist);
+int perf_evlist__apply_toggle(struct perf_evlist *evlist);
 int perf_evlist__apply_filters(struct perf_evlist *evlist);
 
 void __perf_evlist__set_leader(struct list_head *list);
diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c
index 79232d9..b4a3dd2 100644
--- a/tools/perf/util/evsel.c
+++ b/tools/perf/util/evsel.c
@@ -693,6 +693,9 @@
 	 */
 	if (perf_target__none(&opts->target) && perf_evsel__is_group_leader(evsel))
 		attr->enable_on_exec = 1;
+
+	if (evsel->is_toggled)
+		attr->paused = 1;
 }
 
 int perf_evsel__alloc_fd(struct perf_evsel *evsel, int ncpus, int nthreads)
diff --git a/tools/perf/util/evsel.h b/tools/perf/util/evsel.h
index e70415b..69f4183 100644
--- a/tools/perf/util/evsel.h
+++ b/tools/perf/util/evsel.h
@@ -93,6 +93,7 @@
 	/* toggle event config */
 	char			toggle_flag;
 	char			*toggle_name;
+	bool			is_toggled;
 };
 
 #define hists_to_evsel(h) container_of(h, struct perf_evsel, hists)
diff --git a/tools/perf/util/record.c b/tools/perf/util/record.c
index 18d73aa..7f7eeb4 100644
--- a/tools/perf/util/record.c
+++ b/tools/perf/util/record.c
@@ -88,6 +88,8 @@
 	if (evlist->cpus->map[0] < 0)
 		opts->no_inherit = true;
 
+	perf_evlist__mark_toggled(evlist);
+
 	list_for_each_entry(evsel, &evlist->entries, node)
 		perf_evsel__config(evsel, opts);