perf tools: Add optional support for libpfm4
authorStephane Eranian <eranian@google.com>
Tue, 5 May 2020 18:29:43 +0000 (11:29 -0700)
committerArnaldo Carvalho de Melo <acme@redhat.com>
Fri, 29 May 2020 19:51:38 +0000 (16:51 -0300)
This patch links perf with the libpfm4 library if it is available and
LIBPFM4 is passed to the build. The libpfm4 library contains hardware
event tables for all processors supported by perf_events. It is a helper
library that helps convert from a symbolic event name to the event
encoding required by the underlying kernel interface. This library is
open-source and available from: http://perfmon2.sf.net.

With this patch, it is possible to specify full hardware events by name.
Hardware filters are also supported. Events must be specified via the
--pfm-events and not -e option. Both options are active at the same time
and it is possible to mix and match:

  $ perf stat --pfm-events inst_retired:any_p:c=1:i -e cycles ....

One needs to explicitely ask for its inclusion by using the LIBPFM4 make
command line option, ie its opt-in rather than opt-out of feature
detection and build support.

Signed-off-by: Stephane Eranian <eranian@google.com>
Reviewed-by: Ian Rogers <irogers@google.com>
Cc: Adrian Hunter <adrian.hunter@intel.com>
Cc: Alexander Shishkin <alexander.shishkin@linux.intel.com>
Cc: Alexei Starovoitov <ast@kernel.org>
Cc: Alexey Budankov <alexey.budankov@linux.intel.com>
Cc: Andi Kleen <ak@linux.intel.com>
Cc: Andrii Nakryiko <andriin@fb.com>
Cc: Daniel Borkmann <daniel@iogearbox.net>
Cc: Florian Fainelli <f.fainelli@gmail.com>
Cc: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Igor Lubashev <ilubashe@akamai.com>
Cc: Jin Yao <yao.jin@linux.intel.com>
Cc: Jiri Olsa <jolsa@redhat.com>
Cc: Jiwei Sun <jiwei.sun@windriver.com>
Cc: John Garry <john.garry@huawei.com>
Cc: Kan Liang <kan.liang@linux.intel.com>
Cc: Leo Yan <leo.yan@linaro.org>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: Martin KaFai Lau <kafai@fb.com>
Cc: Namhyung Kim <namhyung@kernel.org>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Thomas Gleixner <tglx@linutronix.de>
Cc: Yonghong Song <yhs@fb.com>
Cc: bpf@vger.kernel.org
Cc: netdev@vger.kernel.org
Cc: yuzhoujian <yuzhoujian@didichuxing.com>
Link: http://lore.kernel.org/lkml/20200505182943.218248-2-irogers@google.com
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
19 files changed:
tools/perf/Documentation/perf-record.txt
tools/perf/Documentation/perf-stat.txt
tools/perf/Documentation/perf-top.txt
tools/perf/Makefile.config
tools/perf/Makefile.perf
tools/perf/builtin-record.c
tools/perf/builtin-stat.c
tools/perf/builtin-top.c
tools/perf/tests/Build
tools/perf/tests/builtin-test.c
tools/perf/tests/pfm.c [new file with mode: 0644]
tools/perf/tests/tests.h
tools/perf/util/Build
tools/perf/util/evsel.c
tools/perf/util/evsel.h
tools/perf/util/parse-events.c
tools/perf/util/parse-events.h
tools/perf/util/pfm.c [new file with mode: 0644]
tools/perf/util/pfm.h [new file with mode: 0644]

index 97b1a866ab2265af79e6eb4d40dfc2dab8ea0a38..fa8a5fcd27aba441911bc6c382c2b708b81e7f9e 100644 (file)
@@ -615,6 +615,17 @@ appended unit character - B/K/M/G
        The number of threads to run when synthesizing events for existing processes.
        By default, the number of threads equals 1.
 
+ifdef::HAVE_LIBPFM[]
+--pfm-events events::
+Select a PMU event using libpfm4 syntax (see http://perfmon2.sf.net)
+including support for event filters. For example '--pfm-events
+inst_retired:any_p:u:c=1:i'. More than one event can be passed to the
+option using the comma separator. Hardware events and generic hardware
+events cannot be mixed together. The latter must be used with the -e
+option. The -e option and this one can be mixed and matched.  Events
+can be grouped using the {} notation.
+endif::HAVE_LIBPFM[]
+
 SEE ALSO
 --------
 linkperf:perf-stat[1], linkperf:perf-list[1], linkperf:perf-intel-pt[1]
index 498bead1ef3bd2a71b6a28b161562e8bb2033673..b029ee728a0bceff293809facf0a1764be17b31a 100644 (file)
@@ -71,6 +71,16 @@ report::
 --tid=<tid>::
         stat events on existing thread id (comma separated list)
 
+ifdef::HAVE_LIBPFM[]
+--pfm-events events::
+Select a PMU event using libpfm4 syntax (see http://perfmon2.sf.net)
+including support for event filters. For example '--pfm-events
+inst_retired:any_p:u:c=1:i'. More than one event can be passed to the
+option using the comma separator. Hardware events and generic hardware
+events cannot be mixed together. The latter must be used with the -e
+option. The -e option and this one can be mixed and matched.  Events
+can be grouped using the {} notation.
+endif::HAVE_LIBPFM[]
 
 -a::
 --all-cpus::
index 20227dabc208400f44f864b5c40cdd3f3dfefb1f..ee2024691d46d8dff165a1e5429ac2f62b4d5736 100644 (file)
@@ -329,6 +329,17 @@ Default is to monitor all CPUS.
        The known limitations include exception handing such as
        setjmp/longjmp will have calls/returns not match.
 
+ifdef::HAVE_LIBPFM[]
+--pfm-events events::
+Select a PMU event using libpfm4 syntax (see http://perfmon2.sf.net)
+including support for event filters. For example '--pfm-events
+inst_retired:any_p:u:c=1:i'. More than one event can be passed to the
+option using the comma separator. Hardware events and generic hardware
+events cannot be mixed together. The latter must be used with the -e
+option. The -e option and this one can be mixed and matched.  Events
+can be grouped using the {} notation.
+endif::HAVE_LIBPFM[]
+
 INTERACTIVE PROMPTING KEYS
 --------------------------
 
index dd41bb9f51d4456fc030d053e83148d0ed4c18d8..877ca6be0ed0ec47b93dc8278000995cb6c8c538 100644 (file)
@@ -1022,6 +1022,19 @@ ifdef LIBCLANGLLVM
   endif
 endif
 
+ifdef LIBPFM4
+  $(call feature_check,libpfm4)
+  ifeq ($(feature-libpfm4), 1)
+    CFLAGS += -DHAVE_LIBPFM
+    EXTLIBS += -lpfm
+    ASCIIDOC_EXTRA = -aHAVE_LIBPFM=1
+    $(call detected,CONFIG_LIBPFM4)
+  else
+    msg := $(warning libpfm4 not found, disables libpfm4 support. Please install libpfm4-dev);
+    NO_LIBPFM4 := 1
+  endif
+endif
+
 # Among the variables below, these:
 #   perfexecdir
 #   perf_include_dir
index e3a34af3813010a3ae018e8f1d843c6da780b0dd..86dbb51bb27230e499f232e98f1834b5067ed756 100644 (file)
@@ -121,6 +121,9 @@ include ../scripts/utilities.mak
 # Define NO_SYSCALL_TABLE=1 to disable the use of syscall id to/from name tables
 # generated from the kernel .tbl or unistd.h files and use, if available, libaudit
 # for doing the conversions to/from strings/id.
+#
+# Define LIBPFM4 to enable libpfm4 events extension.
+#
 
 # As per kernel Makefile, avoid funny character set dependencies
 unexport LC_ALL
index ecd478a22c644d2bd3d20398c6b329cdde5a23e3..e108d90ae2edf8704818a7e7c17e70afecddd335 100644 (file)
@@ -45,6 +45,7 @@
 #include "util/units.h"
 #include "util/bpf-event.h"
 #include "util/util.h"
+#include "util/pfm.h"
 #include "asm/bug.h"
 #include "perf.h"
 
@@ -2555,6 +2556,11 @@ static struct option __record_options[] = {
        OPT_UINTEGER(0, "num-thread-synthesize",
                     &record.opts.nr_threads_synthesize,
                     "number of threads to run for event synthesis"),
+#ifdef HAVE_LIBPFM
+       OPT_CALLBACK(0, "pfm-events", &record.evlist, "event",
+               "libpfm4 event selector. use 'perf list' to list available events",
+               parse_libpfm_events_option),
+#endif
        OPT_END()
 };
 
index f789103d830642e632be9de2f458e4a4c1a7edf6..b2b79aa161ddfadd4e9a6bf6fbd46e5103971021 100644 (file)
@@ -66,6 +66,7 @@
 #include "util/time-utils.h"
 #include "util/top.h"
 #include "util/affinity.h"
+#include "util/pfm.h"
 #include "asm/bug.h"
 
 #include <linux/time64.h>
@@ -1012,6 +1013,11 @@ static struct option stat_options[] = {
                    "Use with 'percore' event qualifier to show the event "
                    "counts of one hardware thread by sum up total hardware "
                    "threads of same physical core"),
+#ifdef HAVE_LIBPFM
+       OPT_CALLBACK(0, "pfm-events", &evsel_list, "event",
+               "libpfm4 event selector. use 'perf list' to list available events",
+               parse_libpfm_events_option),
+#endif
        OPT_END()
 };
 
index d991e1c6c9fb77cf263d640b995bada477c7f3d0..13889d73f8dd5f6e79623d3c6bb8550e025565a8 100644 (file)
@@ -53,6 +53,7 @@
 
 #include "util/debug.h"
 #include "util/ordered-events.h"
+#include "util/pfm.h"
 
 #include <assert.h>
 #include <elf.h>
@@ -1575,6 +1576,11 @@ int cmd_top(int argc, const char **argv)
                    "WARNING: should be used on grouped events."),
        OPT_BOOLEAN(0, "stitch-lbr", &top.stitch_lbr,
                    "Enable LBR callgraph stitching approach"),
+#ifdef HAVE_LIBPFM
+       OPT_CALLBACK(0, "pfm-events", &top.evlist, "event",
+               "libpfm4 event selector. use 'perf list' to list available events",
+               parse_libpfm_events_option),
+#endif
        OPTS_EVSWITCH(&top.evswitch),
        OPT_END()
        };
index 446fe14c24cf633ba2d02d00444054537e72787b..cd00498a5dce6f2f7a3f97f6f85a5bfacd5e4af4 100644 (file)
@@ -58,6 +58,7 @@ perf-y += time-utils-test.o
 perf-y += genelf.o
 perf-y += api-io.o
 perf-y += demangle-java-test.o
+perf-y += pfm.o
 
 $(OUTPUT)tests/llvm-src-base.c: tests/bpf-script-example.c tests/Build
        $(call rule_mkdir)
index f1dc2fe93dd8077606c0ec4c2af70299f3c20d96..da5b6cc23f25b7c47aa5fc67120fe9b4742a2534 100644 (file)
@@ -316,6 +316,15 @@ static struct test generic_tests[] = {
                .desc = "Test jit_write_elf",
                .func = test__jit_write_elf,
        },
+       {
+               .desc = "Test libpfm4 support",
+               .func = test__pfm,
+               .subtest = {
+                       .skip_if_fail   = true,
+                       .get_nr         = test__pfm_subtest_get_nr,
+                       .get_desc       = test__pfm_subtest_get_desc,
+               }
+       },
        {
                .desc = "Test api io",
                .func = test__api_io,
diff --git a/tools/perf/tests/pfm.c b/tools/perf/tests/pfm.c
new file mode 100644 (file)
index 0000000..76a5312
--- /dev/null
@@ -0,0 +1,203 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Test support for libpfm4 event encodings.
+ *
+ * Copyright 2020 Google LLC.
+ */
+#include "tests.h"
+#include "util/debug.h"
+#include "util/evlist.h"
+#include "util/pfm.h"
+
+#include <linux/kernel.h>
+
+#ifdef HAVE_LIBPFM
+static int test__pfm_events(void);
+static int test__pfm_group(void);
+#endif
+
+static const struct {
+       int (*func)(void);
+       const char *desc;
+} pfm_testcase_table[] = {
+#ifdef HAVE_LIBPFM
+       {
+               .func = test__pfm_events,
+               .desc = "test of individual --pfm-events",
+       },
+       {
+               .func = test__pfm_group,
+               .desc = "test groups of --pfm-events",
+       },
+#endif
+};
+
+#ifdef HAVE_LIBPFM
+static int count_pfm_events(struct perf_evlist *evlist)
+{
+       struct perf_evsel *evsel;
+       int count = 0;
+
+       perf_evlist__for_each_entry(evlist, evsel) {
+               count++;
+       }
+       return count;
+}
+
+static int test__pfm_events(void)
+{
+       struct evlist *evlist;
+       struct option opt;
+       size_t i;
+       const struct {
+               const char *events;
+               int nr_events;
+       } table[] = {
+               {
+                       .events = "",
+                       .nr_events = 0,
+               },
+               {
+                       .events = "instructions",
+                       .nr_events = 1,
+               },
+               {
+                       .events = "instructions,cycles",
+                       .nr_events = 2,
+               },
+               {
+                       .events = "stereolab",
+                       .nr_events = 0,
+               },
+               {
+                       .events = "instructions,instructions",
+                       .nr_events = 2,
+               },
+               {
+                       .events = "stereolab,instructions",
+                       .nr_events = 0,
+               },
+               {
+                       .events = "instructions,stereolab",
+                       .nr_events = 1,
+               },
+       };
+
+       for (i = 0; i < ARRAY_SIZE(table); i++) {
+               evlist = evlist__new();
+               if (evlist == NULL)
+                       return -ENOMEM;
+
+               opt.value = evlist;
+               parse_libpfm_events_option(&opt,
+                                       table[i].events,
+                                       0);
+               TEST_ASSERT_EQUAL(table[i].events,
+                               count_pfm_events(&evlist->core),
+                               table[i].nr_events);
+               TEST_ASSERT_EQUAL(table[i].events,
+                               evlist->nr_groups,
+                               0);
+
+               evlist__delete(evlist);
+       }
+       return 0;
+}
+
+static int test__pfm_group(void)
+{
+       struct evlist *evlist;
+       struct option opt;
+       size_t i;
+       const struct {
+               const char *events;
+               int nr_events;
+               int nr_groups;
+       } table[] = {
+               {
+                       .events = "{},",
+                       .nr_events = 0,
+                       .nr_groups = 0,
+               },
+               {
+                       .events = "{instructions}",
+                       .nr_events = 1,
+                       .nr_groups = 1,
+               },
+               {
+                       .events = "{instructions},{}",
+                       .nr_events = 1,
+                       .nr_groups = 1,
+               },
+               {
+                       .events = "{},{instructions}",
+                       .nr_events = 0,
+                       .nr_groups = 0,
+               },
+               {
+                       .events = "{instructions},{instructions}",
+                       .nr_events = 2,
+                       .nr_groups = 2,
+               },
+               {
+                       .events = "{instructions,cycles},{instructions,cycles}",
+                       .nr_events = 4,
+                       .nr_groups = 2,
+               },
+               {
+                       .events = "{stereolab}",
+                       .nr_events = 0,
+                       .nr_groups = 0,
+               },
+               {
+                       .events =
+                       "{instructions,cycles},{instructions,stereolab}",
+                       .nr_events = 3,
+                       .nr_groups = 1,
+               },
+       };
+
+       for (i = 0; i < ARRAY_SIZE(table); i++) {
+               evlist = evlist__new();
+               if (evlist == NULL)
+                       return -ENOMEM;
+
+               opt.value = evlist;
+               parse_libpfm_events_option(&opt,
+                                       table[i].events,
+                                       0);
+               TEST_ASSERT_EQUAL(table[i].events,
+                               count_pfm_events(&evlist->core),
+                               table[i].nr_events);
+               TEST_ASSERT_EQUAL(table[i].events,
+                               evlist->nr_groups,
+                               table[i].nr_groups);
+
+               evlist__delete(evlist);
+       }
+       return 0;
+}
+#endif
+
+const char *test__pfm_subtest_get_desc(int i)
+{
+       if (i < 0 || i >= (int)ARRAY_SIZE(pfm_testcase_table))
+               return NULL;
+       return pfm_testcase_table[i].desc;
+}
+
+int test__pfm_subtest_get_nr(void)
+{
+       return (int)ARRAY_SIZE(pfm_testcase_table);
+}
+
+int test__pfm(struct test *test __maybe_unused, int i __maybe_unused)
+{
+#ifdef HAVE_LIBPFM
+       if (i < 0 || i >= (int)ARRAY_SIZE(pfm_testcase_table))
+               return TEST_FAIL;
+       return pfm_testcase_table[i].func();
+#else
+       return TEST_SKIP;
+#endif
+}
index 12856bc6f411859eca583b372ab17697a5f41cb0..76a4e352eaaf7820a8fd073bc848bf58b7418b2f 100644 (file)
@@ -118,6 +118,9 @@ int test__time_utils(struct test *t, int subtest);
 int test__jit_write_elf(struct test *test, int subtest);
 int test__api_io(struct test *test, int subtest);
 int test__demangle_java(struct test *test, int subtest);
+int test__pfm(struct test *test, int subtest);
+const char *test__pfm_subtest_get_desc(int subtest);
+int test__pfm_subtest_get_nr(void);
 
 bool test__bp_signal_is_supported(void);
 bool test__bp_account_is_supported(void);
index 880fcdd1ab11e5bbf19090cb393812a94074178d..329f12345da06acce4418ff502d91a2279679b13 100644 (file)
@@ -183,6 +183,8 @@ perf-$(CONFIG_LIBBPF) += bpf-event.o
 
 perf-$(CONFIG_CXX) += c++/
 
+perf-$(CONFIG_LIBPFM4) += pfm.o
+
 CFLAGS_config.o   += -DETC_PERFCONFIG="BUILD_STR($(ETC_PERFCONFIG_SQ))"
 CFLAGS_llvm-utils.o += -DPERF_INCLUDE_DIR="BUILD_STR($(perf_include_dir_SQ))"
 
index 95b525149949cfee045c44b53f1cf653ab893da0..96e5171dce41e458a897fbae387bc78ef37276f7 100644 (file)
@@ -2417,7 +2417,7 @@ bool evsel__fallback(struct evsel *evsel, int err, char *msg, size_t msgsize)
 
                /* Is there already the separator in the name. */
                if (strchr(name, '/') ||
-                   strchr(name, ':'))
+                   (strchr(name, ':') && !evsel->is_libpfm_event))
                        sep = "";
 
                if (asprintf(&new_name, "%s%su", name, sep) < 0)
index 3a1a814a03196ca00fb9e6c0818f3fc2b632caa6..0f963c2a88a5d3d1734189d5774823a0238de6db 100644 (file)
@@ -76,6 +76,7 @@ struct evsel {
        bool                    ignore_missing_thread;
        bool                    forced_leader;
        bool                    use_uncore_alias;
+       bool                    is_libpfm_event;
        /* parse modifier helper */
        int                     exclude_GH;
        int                     sample_read;
index 27b8e49d690efca34508185002d7eb724d0ccce3..3decbb203846a800adc69a4af766acafd16e4129 100644 (file)
@@ -36,6 +36,7 @@
 #include "metricgroup.h"
 #include "util/evsel_config.h"
 #include "util/event.h"
+#include "util/pfm.h"
 
 #define MAX_NAME_LEN 100
 
@@ -345,6 +346,7 @@ static char *get_config_name(struct list_head *head_terms)
 static struct evsel *
 __add_event(struct list_head *list, int *idx,
            struct perf_event_attr *attr,
+           bool init_attr,
            char *name, struct perf_pmu *pmu,
            struct list_head *config_terms, bool auto_merge_stats,
            const char *cpu_list)
@@ -353,7 +355,8 @@ __add_event(struct list_head *list, int *idx,
        struct perf_cpu_map *cpus = pmu ? pmu->cpus :
                               cpu_list ? perf_cpu_map__new(cpu_list) : NULL;
 
-       event_attr_init(attr);
+       if (init_attr)
+               event_attr_init(attr);
 
        evsel = evsel__new_idx(attr, *idx);
        if (!evsel)
@@ -371,15 +374,25 @@ __add_event(struct list_head *list, int *idx,
        if (config_terms)
                list_splice(config_terms, &evsel->config_terms);
 
-       list_add_tail(&evsel->core.node, list);
+       if (list)
+               list_add_tail(&evsel->core.node, list);
+
        return evsel;
 }
 
+struct evsel *parse_events__add_event(int idx, struct perf_event_attr *attr,
+                                       char *name, struct perf_pmu *pmu)
+{
+       return __add_event(NULL, &idx, attr, false, name, pmu, NULL, false,
+                          NULL);
+}
+
 static int add_event(struct list_head *list, int *idx,
                     struct perf_event_attr *attr, char *name,
                     struct list_head *config_terms)
 {
-       return __add_event(list, idx, attr, name, NULL, config_terms, false, NULL) ? 0 : -ENOMEM;
+       return __add_event(list, idx, attr, true, name, NULL, config_terms,
+                          false, NULL) ? 0 : -ENOMEM;
 }
 
 static int add_event_tool(struct list_head *list, int *idx,
@@ -391,7 +404,8 @@ static int add_event_tool(struct list_head *list, int *idx,
                .config = PERF_COUNT_SW_DUMMY,
        };
 
-       evsel = __add_event(list, idx, &attr, NULL, NULL, NULL, false, "0");
+       evsel = __add_event(list, idx, &attr, true, NULL, NULL, NULL, false,
+                           "0");
        if (!evsel)
                return -ENOMEM;
        evsel->tool_event = tool_event;
@@ -1458,8 +1472,8 @@ int parse_events_add_pmu(struct parse_events_state *parse_state,
 
        if (!head_config) {
                attr.type = pmu->type;
-               evsel = __add_event(list, &parse_state->idx, &attr, NULL, pmu, NULL,
-                                   auto_merge_stats, NULL);
+               evsel = __add_event(list, &parse_state->idx, &attr, true, NULL,
+                                   pmu, NULL, auto_merge_stats, NULL);
                if (evsel) {
                        evsel->pmu_name = name ? strdup(name) : NULL;
                        evsel->use_uncore_alias = use_uncore_alias;
@@ -1514,7 +1528,7 @@ int parse_events_add_pmu(struct parse_events_state *parse_state,
                return -EINVAL;
        }
 
-       evsel = __add_event(list, &parse_state->idx, &attr,
+       evsel = __add_event(list, &parse_state->idx, &attr, true,
                            get_config_name(head_config), pmu,
                            &config_terms, auto_merge_stats, NULL);
        if (evsel) {
@@ -2846,6 +2860,8 @@ void print_events(const char *event_glob, bool name_only, bool quiet_flag,
        print_sdt_events(NULL, NULL, name_only);
 
        metricgroup__print(true, true, NULL, name_only, details_flag);
+
+       print_libpfm_events(name_only, long_desc);
 }
 
 int parse_events__is_hardcoded_term(struct parse_events_term *term)
index d60510e0609f821fc7ae5f582fca9aef462700ed..1fe23a2f9b36ec7c22d8d11570230ca4de499ea1 100644 (file)
@@ -17,6 +17,7 @@ struct evlist;
 struct parse_events_error;
 
 struct option;
+struct perf_pmu;
 
 struct tracepoint_path {
        char *system;
@@ -188,6 +189,9 @@ int parse_events_add_pmu(struct parse_events_state *parse_state,
                         bool auto_merge_stats,
                         bool use_alias);
 
+struct evsel *parse_events__add_event(int idx, struct perf_event_attr *attr,
+                                       char *name, struct perf_pmu *pmu);
+
 int parse_events_multi_pmu_add(struct parse_events_state *parse_state,
                               char *str,
                               struct list_head **listp);
diff --git a/tools/perf/util/pfm.c b/tools/perf/util/pfm.c
new file mode 100644 (file)
index 0000000..d735acb
--- /dev/null
@@ -0,0 +1,281 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Support for libpfm4 event encoding.
+ *
+ * Copyright 2020 Google LLC.
+ */
+#include "util/cpumap.h"
+#include "util/debug.h"
+#include "util/event.h"
+#include "util/evlist.h"
+#include "util/evsel.h"
+#include "util/parse-events.h"
+#include "util/pmu.h"
+#include "util/pfm.h"
+
+#include <string.h>
+#include <linux/kernel.h>
+#include <perfmon/pfmlib_perf_event.h>
+
+static void libpfm_initialize(void)
+{
+       int ret;
+
+       ret = pfm_initialize();
+       if (ret != PFM_SUCCESS) {
+               ui__warning("libpfm failed to initialize: %s\n",
+                       pfm_strerror(ret));
+       }
+}
+
+int parse_libpfm_events_option(const struct option *opt, const char *str,
+                       int unset __maybe_unused)
+{
+       struct evlist *evlist = *(struct evlist **)opt->value;
+       struct perf_event_attr attr;
+       struct perf_pmu *pmu;
+       struct evsel *evsel, *grp_leader = NULL;
+       char *p, *q, *p_orig;
+       const char *sep;
+       int grp_evt = -1;
+       int ret;
+
+       libpfm_initialize();
+
+       p_orig = p = strdup(str);
+       if (!p)
+               return -1;
+       /*
+        * force loading of the PMU list
+        */
+       perf_pmu__scan(NULL);
+
+       for (q = p; strsep(&p, ",{}"); q = p) {
+               sep = p ? str + (p - p_orig - 1) : "";
+               if (*sep == '{') {
+                       if (grp_evt > -1) {
+                               ui__error(
+                                       "nested event groups not supported\n");
+                               goto error;
+                       }
+                       grp_evt++;
+               }
+
+               /* no event */
+               if (*q == '\0')
+                       continue;
+
+               memset(&attr, 0, sizeof(attr));
+               event_attr_init(&attr);
+
+               ret = pfm_get_perf_event_encoding(q, PFM_PLM0|PFM_PLM3,
+                                               &attr, NULL, NULL);
+
+               if (ret != PFM_SUCCESS) {
+                       ui__error("failed to parse event %s : %s\n", str,
+                                 pfm_strerror(ret));
+                       goto error;
+               }
+
+               pmu = perf_pmu__find_by_type((unsigned int)attr.type);
+               evsel = parse_events__add_event(evlist->core.nr_entries,
+                                               &attr, q, pmu);
+               if (evsel == NULL)
+                       goto error;
+
+               evsel->is_libpfm_event = true;
+
+               evlist__add(evlist, evsel);
+
+               if (grp_evt == 0)
+                       grp_leader = evsel;
+
+               if (grp_evt > -1) {
+                       evsel->leader = grp_leader;
+                       grp_leader->core.nr_members++;
+                       grp_evt++;
+               }
+
+               if (*sep == '}') {
+                       if (grp_evt < 0) {
+                               ui__error(
+                                  "cannot close a non-existing event group\n");
+                               goto error;
+                       }
+                       evlist->nr_groups++;
+                       grp_leader = NULL;
+                       grp_evt = -1;
+               }
+       }
+       return 0;
+error:
+       free(p_orig);
+       return -1;
+}
+
+static const char *srcs[PFM_ATTR_CTRL_MAX] = {
+       [PFM_ATTR_CTRL_UNKNOWN] = "???",
+       [PFM_ATTR_CTRL_PMU] = "PMU",
+       [PFM_ATTR_CTRL_PERF_EVENT] = "perf_event",
+};
+
+static void
+print_attr_flags(pfm_event_attr_info_t *info)
+{
+       int n = 0;
+
+       if (info->is_dfl) {
+               printf("[default] ");
+               n++;
+       }
+
+       if (info->is_precise) {
+               printf("[precise] ");
+               n++;
+       }
+
+       if (!n)
+               printf("- ");
+}
+
+static void
+print_libpfm_events_detailed(pfm_event_info_t *info, bool long_desc)
+{
+       pfm_event_attr_info_t ainfo;
+       const char *src;
+       int j, ret;
+
+       ainfo.size = sizeof(ainfo);
+
+       printf("  %s\n", info->name);
+       printf("    [%s]\n", info->desc);
+       if (long_desc) {
+               if (info->equiv)
+                       printf("      Equiv: %s\n", info->equiv);
+
+               printf("      Code  : 0x%"PRIx64"\n", info->code);
+       }
+       pfm_for_each_event_attr(j, info) {
+               ret = pfm_get_event_attr_info(info->idx, j,
+                                             PFM_OS_PERF_EVENT_EXT, &ainfo);
+               if (ret != PFM_SUCCESS)
+                       continue;
+
+               if (ainfo.type == PFM_ATTR_UMASK) {
+                       printf("      %s:%s\n", info->name, ainfo.name);
+                       printf("        [%s]\n", ainfo.desc);
+               }
+
+               if (!long_desc)
+                       continue;
+
+               if (ainfo.ctrl >= PFM_ATTR_CTRL_MAX)
+                       ainfo.ctrl = PFM_ATTR_CTRL_UNKNOWN;
+
+               src = srcs[ainfo.ctrl];
+               switch (ainfo.type) {
+               case PFM_ATTR_UMASK:
+                       printf("        Umask : 0x%02"PRIx64" : %s: ",
+                               ainfo.code, src);
+                       print_attr_flags(&ainfo);
+                       putchar('\n');
+                       break;
+               case PFM_ATTR_MOD_BOOL:
+                       printf("      Modif : %s: [%s] : %s (boolean)\n", src,
+                               ainfo.name, ainfo.desc);
+                       break;
+               case PFM_ATTR_MOD_INTEGER:
+                       printf("      Modif : %s: [%s] : %s (integer)\n", src,
+                               ainfo.name, ainfo.desc);
+                       break;
+               case PFM_ATTR_NONE:
+               case PFM_ATTR_RAW_UMASK:
+               case PFM_ATTR_MAX:
+               default:
+                       printf("      Attr  : %s: [%s] : %s\n", src,
+                               ainfo.name, ainfo.desc);
+               }
+       }
+}
+
+/*
+ * list all pmu::event:umask, pmu::event
+ * printed events may not be all valid combinations of umask for an event
+ */
+static void
+print_libpfm_events_raw(pfm_pmu_info_t *pinfo, pfm_event_info_t *info)
+{
+       pfm_event_attr_info_t ainfo;
+       int j, ret;
+       bool has_umask = false;
+
+       ainfo.size = sizeof(ainfo);
+
+       pfm_for_each_event_attr(j, info) {
+               ret = pfm_get_event_attr_info(info->idx, j,
+                                             PFM_OS_PERF_EVENT_EXT, &ainfo);
+               if (ret != PFM_SUCCESS)
+                       continue;
+
+               if (ainfo.type != PFM_ATTR_UMASK)
+                       continue;
+
+               printf("%s::%s:%s\n", pinfo->name, info->name, ainfo.name);
+               has_umask = true;
+       }
+       if (!has_umask)
+               printf("%s::%s\n", pinfo->name, info->name);
+}
+
+void print_libpfm_events(bool name_only, bool long_desc)
+{
+       pfm_event_info_t info;
+       pfm_pmu_info_t pinfo;
+       int i, p, ret;
+
+       libpfm_initialize();
+
+       /* initialize to zero to indicate ABI version */
+       info.size  = sizeof(info);
+       pinfo.size = sizeof(pinfo);
+
+       if (!name_only)
+               puts("\nList of pre-defined events (to be used in --pfm-events):\n");
+
+       pfm_for_all_pmus(p) {
+               bool printed_pmu = false;
+
+               ret = pfm_get_pmu_info(p, &pinfo);
+               if (ret != PFM_SUCCESS)
+                       continue;
+
+               /* only print events that are supported by host HW */
+               if (!pinfo.is_present)
+                       continue;
+
+               /* handled by perf directly */
+               if (pinfo.pmu == PFM_PMU_PERF_EVENT)
+                       continue;
+
+               for (i = pinfo.first_event; i != -1;
+                    i = pfm_get_event_next(i)) {
+
+                       ret = pfm_get_event_info(i, PFM_OS_PERF_EVENT_EXT,
+                                               &info);
+                       if (ret != PFM_SUCCESS)
+                               continue;
+
+                       if (!name_only && !printed_pmu) {
+                               printf("%s:\n", pinfo.name);
+                               printed_pmu = true;
+                       }
+
+                       if (!name_only)
+                               print_libpfm_events_detailed(&info, long_desc);
+                       else
+                               print_libpfm_events_raw(&pinfo, &info);
+               }
+               if (!name_only && printed_pmu)
+                       putchar('\n');
+       }
+}
diff --git a/tools/perf/util/pfm.h b/tools/perf/util/pfm.h
new file mode 100644 (file)
index 0000000..7d70dda
--- /dev/null
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Support for libpfm4 event encoding.
+ *
+ * Copyright 2020 Google LLC.
+ */
+#ifndef __PERF_PFM_H
+#define __PERF_PFM_H
+
+#include <subcmd/parse-options.h>
+
+#ifdef HAVE_LIBPFM
+int parse_libpfm_events_option(const struct option *opt, const char *str,
+                       int unset);
+
+void print_libpfm_events(bool name_only, bool long_desc);
+
+#else
+#include <linux/compiler.h>
+
+static inline int parse_libpfm_events_option(
+       const struct option *opt __maybe_unused,
+       const char *str __maybe_unused,
+       int unset __maybe_unused)
+{
+       return 0;
+}
+
+static inline void print_libpfm_events(bool name_only __maybe_unused,
+                                      bool long_desc __maybe_unused)
+{
+}
+
+#endif
+
+
+#endif /* __PERF_PFM_H */