#include "util/pmu-hybrid.h"
 #include "util/debug.h"
 #include "util/metricgroup.h"
+#include "util/string2.h"
+#include "util/strlist.h"
 #include <subcmd/pager.h>
 #include <subcmd/parse-options.h>
 #include <stdio.h>
 
-static bool desc_flag = true;
-static bool details_flag;
+/**
+ * struct print_state - State and configuration passed to the default_print
+ * functions.
+ */
+struct print_state {
+       /**
+        * @pmu_glob: Optionally restrict PMU and metric matching to PMU or
+        * debugfs subsystem name.
+        */
+       char *pmu_glob;
+       /** @event_glob: Optional pattern matching glob. */
+       char *event_glob;
+       /** @name_only: Print event or metric names only. */
+       bool name_only;
+       /** @desc: Print the event or metric description. */
+       bool desc;
+       /** @long_desc: Print longer event or metric description. */
+       bool long_desc;
+       /** @deprecated: Print deprecated events or metrics. */
+       bool deprecated;
+       /**
+        * @detailed: Print extra information on the perf event such as names
+        * and expressions used internally by events.
+        */
+       bool detailed;
+       /** @metrics: Controls printing of metric and metric groups. */
+       bool metrics;
+       /** @metricgroups: Controls printing of metric and metric groups. */
+       bool metricgroups;
+       /** @last_topic: The last printed event topic. */
+       char *last_topic;
+       /** @last_metricgroups: The last printed metric group. */
+       char *last_metricgroups;
+       /** @visited_metrics: Metrics that are printed to avoid duplicates. */
+       struct strlist *visited_metrics;
+};
+
+static void default_print_start(void *ps)
+{
+       struct print_state *print_state = ps;
+
+       if (!print_state->name_only && pager_in_use())
+               printf("\nList of pre-defined events (to be used in -e or -M):\n\n");
+}
+
+static void default_print_end(void *print_state __maybe_unused) {}
+
+static void wordwrap(const char *s, int start, int max, int corr)
+{
+       int column = start;
+       int n;
+
+       while (*s) {
+               int wlen = strcspn(s, " \t");
+
+               if (column + wlen >= max && column > start) {
+                       printf("\n%*s", start, "");
+                       column = start + corr;
+               }
+               n = printf("%s%.*s", column > start ? " " : "", wlen, s);
+               if (n <= 0)
+                       break;
+               s += wlen;
+               column += n;
+               s = skip_spaces(s);
+       }
+}
+
+static void default_print_event(void *ps, const char *pmu_name, const char *topic,
+                               const char *event_name, const char *event_alias,
+                               const char *scale_unit __maybe_unused,
+                               bool deprecated, const char *event_type_desc,
+                               const char *desc, const char *long_desc,
+                               const char *encoding_desc,
+                               const char *metric_name, const char *metric_expr)
+{
+       struct print_state *print_state = ps;
+       int pos;
+
+       if (deprecated && !print_state->deprecated)
+               return;
+
+       if (print_state->pmu_glob && pmu_name && !strglobmatch(pmu_name, print_state->pmu_glob))
+               return;
+
+       if (print_state->event_glob &&
+           (!event_name || !strglobmatch(event_name, print_state->event_glob)) &&
+           (!event_alias || !strglobmatch(event_alias, print_state->event_glob)) &&
+           (!topic || !strglobmatch_nocase(topic, print_state->event_glob)))
+               return;
+
+       if (print_state->name_only) {
+               if (event_alias && strlen(event_alias))
+                       printf("%s ", event_alias);
+               else
+                       printf("%s ", event_name);
+               return;
+       }
+
+       if (strcmp(print_state->last_topic, topic ?: "")) {
+               if (topic)
+                       printf("\n%s:\n", topic);
+               free(print_state->last_topic);
+               print_state->last_topic = strdup(topic ?: "");
+       }
+
+       if (event_alias && strlen(event_alias))
+               pos = printf("  %s OR %s", event_name, event_alias);
+       else
+               pos = printf("  %s", event_name);
+
+       if (!topic && event_type_desc) {
+               for (; pos < 53; pos++)
+                       putchar(' ');
+               printf("[%s]\n", event_type_desc);
+       } else
+               putchar('\n');
+
+       if (desc && print_state->desc) {
+               printf("%*s", 8, "[");
+               wordwrap(desc, 8, pager_get_columns(), 0);
+               printf("]\n");
+       }
+
+       if (long_desc && print_state->long_desc) {
+               printf("%*s", 8, "[");
+               wordwrap(long_desc, 8, pager_get_columns(), 0);
+               printf("]\n");
+       }
+
+       if (print_state->detailed && encoding_desc) {
+               printf("%*s%s", 8, "", encoding_desc);
+               if (metric_name)
+                       printf(" MetricName: %s", metric_name);
+               if (metric_expr)
+                       printf(" MetricExpr: %s", metric_expr);
+               putchar('\n');
+       }
+}
+
+static void default_print_metric(void *ps,
+                               const char *group,
+                               const char *name,
+                               const char *desc,
+                               const char *long_desc,
+                               const char *expr,
+                               const char *unit __maybe_unused)
+{
+       struct print_state *print_state = ps;
+
+       if (print_state->event_glob &&
+           (!print_state->metrics || !name || !strglobmatch(name, print_state->event_glob)) &&
+           (!print_state->metricgroups || !group || !strglobmatch(group, print_state->event_glob)))
+               return;
+
+       if (!print_state->name_only && !print_state->last_metricgroups) {
+               if (print_state->metricgroups) {
+                       printf("\nMetric Groups:\n");
+                       if (!print_state->metrics)
+                               putchar('\n');
+               } else {
+                       printf("\nMetrics:\n\n");
+               }
+       }
+       if (!print_state->last_metricgroups ||
+           strcmp(print_state->last_metricgroups, group ?: "")) {
+               if (group && print_state->metricgroups) {
+                       if (print_state->name_only)
+                               printf("%s ", group);
+                       else if (print_state->metrics)
+                               printf("\n%s:\n", group);
+                       else
+                               printf("%s\n", group);
+               }
+               free(print_state->last_metricgroups);
+               print_state->last_metricgroups = strdup(group ?: "");
+       }
+       if (!print_state->metrics)
+               return;
+
+       if (print_state->name_only) {
+               if (print_state->metrics &&
+                   !strlist__has_entry(print_state->visited_metrics, name)) {
+                       printf("%s ", name);
+                       strlist__add(print_state->visited_metrics, name);
+               }
+               return;
+       }
+       printf("  %s\n", name);
+
+       if (desc && print_state->desc) {
+               printf("%*s", 8, "[");
+               wordwrap(desc, 8, pager_get_columns(), 0);
+               printf("]\n");
+       }
+       if (long_desc && print_state->long_desc) {
+               printf("%*s", 8, "[");
+               wordwrap(long_desc, 8, pager_get_columns(), 0);
+               printf("]\n");
+       }
+       if (expr && print_state->detailed) {
+               printf("%*s", 8, "[");
+               wordwrap(expr, 8, pager_get_columns(), 0);
+               printf("]\n");
+       }
+}
 
 int cmd_list(int argc, const char **argv)
 {
        int i, ret = 0;
-       bool raw_dump = false;
-       bool long_desc_flag = false;
-       bool deprecated = false;
-       char *pmu_name = NULL;
+       struct print_state ps = {};
+       struct print_callbacks print_cb = {
+               .print_start = default_print_start,
+               .print_end = default_print_end,
+               .print_event = default_print_event,
+               .print_metric = default_print_metric,
+       };
        const char *hybrid_name = NULL;
        const char *unit_name = NULL;
        struct option list_options[] = {
-               OPT_BOOLEAN(0, "raw-dump", &raw_dump, "Dump raw events"),
-               OPT_BOOLEAN('d', "desc", &desc_flag,
+               OPT_BOOLEAN(0, "raw-dump", &ps.name_only, "Dump raw events"),
+               OPT_BOOLEAN('d', "desc", &ps.desc,
                            "Print extra event descriptions. --no-desc to not print."),
-               OPT_BOOLEAN('v', "long-desc", &long_desc_flag,
+               OPT_BOOLEAN('v', "long-desc", &ps.long_desc,
                            "Print longer event descriptions."),
-               OPT_BOOLEAN(0, "details", &details_flag,
+               OPT_BOOLEAN(0, "details", &ps.detailed,
                            "Print information on the perf event names and expressions used internally by events."),
-               OPT_BOOLEAN(0, "deprecated", &deprecated,
+               OPT_BOOLEAN(0, "deprecated", &ps.deprecated,
                            "Print deprecated events."),
                OPT_STRING(0, "cputype", &hybrid_name, "hybrid cpu type",
                           "Limit PMU or metric printing to the given hybrid PMU (e.g. core or atom)."),
 
        setup_pager();
 
-       if (!raw_dump && pager_in_use())
-               printf("\nList of pre-defined events (to be used in -e or -M):\n\n");
+       if (!ps.name_only)
+               setup_pager();
 
+       ps.desc = !ps.long_desc;
+       ps.last_topic = strdup("");
+       assert(ps.last_topic);
+       ps.visited_metrics = strlist__new(NULL, NULL);
+       assert(ps.visited_metrics);
        if (unit_name)
-               pmu_name = strdup(unit_name);
+               ps.pmu_glob = strdup(unit_name);
        else if (hybrid_name) {
-               pmu_name = perf_pmu__hybrid_type_to_pmu(hybrid_name);
-               if (!pmu_name)
+               ps.pmu_glob = perf_pmu__hybrid_type_to_pmu(hybrid_name);
+               if (!ps.pmu_glob)
                        pr_warning("WARNING: hybrid cputype is not supported!\n");
        }
 
+       print_cb.print_start(&ps);
+
        if (argc == 0) {
-               print_events(NULL, raw_dump, !desc_flag, long_desc_flag,
-                               details_flag, deprecated, pmu_name);
+               ps.metrics = true;
+               ps.metricgroups = true;
+               print_events(&print_cb, &ps);
                goto out;
        }
 
                char *sep, *s;
 
                if (strcmp(argv[i], "tracepoint") == 0)
-                       print_tracepoint_events(NULL, NULL, raw_dump);
+                       print_tracepoint_events(&print_cb, &ps);
                else if (strcmp(argv[i], "hw") == 0 ||
                         strcmp(argv[i], "hardware") == 0)
-                       print_symbol_events(NULL, PERF_TYPE_HARDWARE,
-                                       event_symbols_hw, PERF_COUNT_HW_MAX, raw_dump);
+                       print_symbol_events(&print_cb, &ps, PERF_TYPE_HARDWARE,
+                                       event_symbols_hw, PERF_COUNT_HW_MAX);
                else if (strcmp(argv[i], "sw") == 0 ||
                         strcmp(argv[i], "software") == 0) {
-                       print_symbol_events(NULL, PERF_TYPE_SOFTWARE,
-                                       event_symbols_sw, PERF_COUNT_SW_MAX, raw_dump);
-                       print_tool_events(NULL, raw_dump);
+                       print_symbol_events(&print_cb, &ps, PERF_TYPE_SOFTWARE,
+                                       event_symbols_sw, PERF_COUNT_SW_MAX);
+                       print_tool_events(&print_cb, &ps);
                } else if (strcmp(argv[i], "cache") == 0 ||
                         strcmp(argv[i], "hwcache") == 0)
-                       print_hwcache_events(NULL, raw_dump);
+                       print_hwcache_events(&print_cb, &ps);
                else if (strcmp(argv[i], "pmu") == 0)
-                       print_pmu_events(NULL, raw_dump, !desc_flag,
-                                               long_desc_flag, details_flag,
-                                               deprecated, pmu_name);
+                       print_pmu_events(&print_cb, &ps);
                else if (strcmp(argv[i], "sdt") == 0)
-                       print_sdt_events(NULL, NULL, raw_dump);
-               else if (strcmp(argv[i], "metric") == 0 || strcmp(argv[i], "metrics") == 0)
-                       metricgroup__print(true, false, NULL, raw_dump, details_flag, pmu_name);
-               else if (strcmp(argv[i], "metricgroup") == 0 || strcmp(argv[i], "metricgroups") == 0)
-                       metricgroup__print(false, true, NULL, raw_dump, details_flag, pmu_name);
-               else if ((sep = strchr(argv[i], ':')) != NULL) {
+                       print_sdt_events(&print_cb, &ps);
+               else if (strcmp(argv[i], "metric") == 0 || strcmp(argv[i], "metrics") == 0) {
+                       ps.metricgroups = false;
+                       ps.metrics = true;
+                       metricgroup__print(&print_cb, &ps);
+               } else if (strcmp(argv[i], "metricgroup") == 0 ||
+                          strcmp(argv[i], "metricgroups") == 0) {
+                       ps.metricgroups = true;
+                       ps.metrics = false;
+                       metricgroup__print(&print_cb, &ps);
+               } else if ((sep = strchr(argv[i], ':')) != NULL) {
                        int sep_idx;
+                       char *old_pmu_glob = ps.pmu_glob;
 
                        sep_idx = sep - argv[i];
                        s = strdup(argv[i]);
                        }
 
                        s[sep_idx] = '\0';
-                       print_tracepoint_events(s, s + sep_idx + 1, raw_dump);
-                       print_sdt_events(s, s + sep_idx + 1, raw_dump);
-                       metricgroup__print(true, true, s, raw_dump, details_flag, pmu_name);
+                       ps.pmu_glob = s;
+                       ps.event_glob = s + sep_idx + 1;
+                       print_tracepoint_events(&print_cb, &ps);
+                       print_sdt_events(&print_cb, &ps);
+                       ps.metrics = true;
+                       ps.metricgroups = true;
+                       metricgroup__print(&print_cb, &ps);
                        free(s);
+                       ps.pmu_glob = old_pmu_glob;
                } else {
                        if (asprintf(&s, "*%s*", argv[i]) < 0) {
                                printf("Critical: Not enough memory! Trying to continue...\n");
                                continue;
                        }
-                       print_symbol_events(s, PERF_TYPE_HARDWARE,
-                                           event_symbols_hw, PERF_COUNT_HW_MAX, raw_dump);
-                       print_symbol_events(s, PERF_TYPE_SOFTWARE,
-                                           event_symbols_sw, PERF_COUNT_SW_MAX, raw_dump);
-                       print_tool_events(s, raw_dump);
-                       print_hwcache_events(s, raw_dump);
-                       print_pmu_events(s, raw_dump, !desc_flag,
-                                               long_desc_flag,
-                                               details_flag,
-                                               deprecated,
-                                               pmu_name);
-                       print_tracepoint_events(NULL, s, raw_dump);
-                       print_sdt_events(NULL, s, raw_dump);
-                       metricgroup__print(true, true, s, raw_dump, details_flag, pmu_name);
+                       ps.event_glob = s;
+                       print_symbol_events(&print_cb, &ps, PERF_TYPE_HARDWARE,
+                                       event_symbols_hw, PERF_COUNT_HW_MAX);
+                       print_symbol_events(&print_cb, &ps, PERF_TYPE_SOFTWARE,
+                                       event_symbols_sw, PERF_COUNT_SW_MAX);
+                       print_tool_events(&print_cb, &ps);
+                       print_hwcache_events(&print_cb, &ps);
+                       print_pmu_events(&print_cb, &ps);
+                       print_tracepoint_events(&print_cb, &ps);
+                       print_sdt_events(&print_cb, &ps);
+                       ps.metrics = true;
+                       ps.metricgroups = true;
+                       metricgroup__print(&print_cb, &ps);
                        free(s);
                }
        }
 
 out:
-       free(pmu_name);
+       print_cb.print_end(&ps);
+       free(ps.pmu_glob);
+       free(ps.last_topic);
+       free(ps.last_metricgroups);
+       strlist__delete(ps.visited_metrics);
        return ret;
 }
 
 #include "strbuf.h"
 #include "pmu.h"
 #include "pmu-hybrid.h"
+#include "print-events.h"
 #include "expr.h"
 #include "rblist.h"
 #include <string.h>
               match_metric(pe->metric_name, metric);
 }
 
+/** struct mep - RB-tree node for building printing information. */
 struct mep {
+       /** nd - RB-tree element. */
        struct rb_node nd;
-       const char *name;
-       struct strlist *metrics;
+       /** @metric_group: Owned metric group name, separated others with ';'. */
+       char *metric_group;
+       const char *metric_name;
+       const char *metric_desc;
+       const char *metric_long_desc;
+       const char *metric_expr;
+       const char *metric_unit;
 };
 
 static int mep_cmp(struct rb_node *rb_node, const void *entry)
 {
        struct mep *a = container_of(rb_node, struct mep, nd);
        struct mep *b = (struct mep *)entry;
+       int ret;
 
-       return strcmp(a->name, b->name);
+       ret = strcmp(a->metric_group, b->metric_group);
+       if (ret)
+               return ret;
+
+       return strcmp(a->metric_name, b->metric_name);
 }
 
-static struct rb_node *mep_new(struct rblist *rl __maybe_unused,
-                                       const void *entry)
+static struct rb_node *mep_new(struct rblist *rl __maybe_unused, const void *entry)
 {
        struct mep *me = malloc(sizeof(struct mep));
 
        if (!me)
                return NULL;
+
        memcpy(me, entry, sizeof(struct mep));
-       me->name = strdup(me->name);
-       if (!me->name)
-               goto out_me;
-       me->metrics = strlist__new(NULL, NULL);
-       if (!me->metrics)
-               goto out_name;
        return &me->nd;
-out_name:
-       zfree(&me->name);
-out_me:
+}
+
+static void mep_delete(struct rblist *rl __maybe_unused,
+                      struct rb_node *nd)
+{
+       struct mep *me = container_of(nd, struct mep, nd);
+
+       zfree(&me->metric_group);
        free(me);
-       return NULL;
 }
 
-static struct mep *mep_lookup(struct rblist *groups, const char *name)
+static struct mep *mep_lookup(struct rblist *groups, const char *metric_group,
+                             const char *metric_name)
 {
        struct rb_node *nd;
        struct mep me = {
-               .name = name
+               .metric_group = strdup(metric_group),
+               .metric_name = metric_name,
        };
        nd = rblist__find(groups, &me);
-       if (nd)
+       if (nd) {
+               free(me.metric_group);
                return container_of(nd, struct mep, nd);
+       }
        rblist__add_node(groups, &me);
        nd = rblist__find(groups, &me);
        if (nd)
        return NULL;
 }
 
-static void mep_delete(struct rblist *rl __maybe_unused,
-                      struct rb_node *nd)
-{
-       struct mep *me = container_of(nd, struct mep, nd);
-
-       strlist__delete(me->metrics);
-       zfree(&me->name);
-       free(me);
-}
-
-static void metricgroup__print_strlist(struct strlist *metrics, bool raw)
-{
-       struct str_node *sn;
-       int n = 0;
-
-       strlist__for_each_entry (sn, metrics) {
-               if (raw)
-                       printf("%s%s", n > 0 ? " " : "", sn->s);
-               else
-                       printf("  %s\n", sn->s);
-               n++;
-       }
-       if (raw)
-               putchar('\n');
-}
-
-static int metricgroup__print_pmu_event(const struct pmu_event *pe,
-                                       bool metricgroups, char *filter,
-                                       bool raw, bool details,
-                                       struct rblist *groups,
-                                       struct strlist *metriclist)
+static int metricgroup__add_to_mep_groups(const struct pmu_event *pe,
+                                       struct rblist *groups)
 {
        const char *g;
        char *omg, *mg;
 
-       g = pe->metric_group;
-       if (!g && pe->metric_name) {
-               if (pe->name)
-                       return 0;
-               g = "No_group";
-       }
-
-       if (!g)
-               return 0;
-
-       mg = strdup(g);
-
+       mg = strdup(pe->metric_group ?: "No_group");
        if (!mg)
                return -ENOMEM;
        omg = mg;
        while ((g = strsep(&mg, ";")) != NULL) {
                struct mep *me;
-               char *s;
 
                g = skip_spaces(g);
-               if (*g == 0)
-                       g = "No_group";
-               if (filter && !strstr(g, filter))
-                       continue;
-               if (raw)
-                       s = (char *)pe->metric_name;
-               else {
-                       if (asprintf(&s, "%s\n%*s%s]",
-                                    pe->metric_name, 8, "[", pe->desc) < 0)
-                               return -1;
-                       if (details) {
-                               if (asprintf(&s, "%s\n%*s%s]",
-                                            s, 8, "[", pe->metric_expr) < 0)
-                                       return -1;
-                       }
-               }
-
-               if (!s)
-                       continue;
+               if (strlen(g))
+                       me = mep_lookup(groups, g, pe->metric_name);
+               else
+                       me = mep_lookup(groups, "No_group", pe->metric_name);
 
-               if (!metricgroups) {
-                       strlist__add(metriclist, s);
-               } else {
-                       me = mep_lookup(groups, g);
-                       if (!me)
-                               continue;
-                       strlist__add(me->metrics, s);
+               if (me) {
+                       me->metric_desc = pe->desc;
+                       me->metric_long_desc = pe->long_desc;
+                       me->metric_expr = pe->metric_expr;
+                       me->metric_unit = pe->unit;
                }
-
-               if (!raw)
-                       free(s);
        }
        free(omg);
 
        return 0;
 }
 
-struct metricgroup_print_sys_idata {
-       struct strlist *metriclist;
-       char *filter;
-       struct rblist *groups;
-       bool metricgroups;
-       bool raw;
-       bool details;
-};
-
 struct metricgroup_iter_data {
        pmu_event_iter_fn fn;
        void *data;
 
                return d->fn(pe, table, d->data);
        }
-
        return 0;
 }
 
-static int metricgroup__print_sys_event_iter(const struct pmu_event *pe,
-                                            const struct pmu_events_table *table __maybe_unused,
-                                            void *data)
-{
-       struct metricgroup_print_sys_idata *d = data;
-
-       return metricgroup__print_pmu_event(pe, d->metricgroups, d->filter, d->raw,
-                                    d->details, d->groups, d->metriclist);
-}
-
-struct metricgroup_print_data {
-       const char *pmu_name;
-       struct strlist *metriclist;
-       char *filter;
-       struct rblist *groups;
-       bool metricgroups;
-       bool raw;
-       bool details;
-};
-
-static int metricgroup__print_callback(const struct pmu_event *pe,
-                                      const struct pmu_events_table *table __maybe_unused,
-                                      void *vdata)
+static int metricgroup__add_to_mep_groups_callback(const struct pmu_event *pe,
+                                               const struct pmu_events_table *table __maybe_unused,
+                                               void *vdata)
 {
-       struct metricgroup_print_data *data = vdata;
-       const char *pmu = pe->pmu ?: "cpu";
+       struct rblist *groups = vdata;
 
-       if (!pe->metric_expr)
-               return 0;
-
-       if (data->pmu_name && strcmp(data->pmu_name, pmu))
+       if (!pe->metric_name)
                return 0;
 
-       return metricgroup__print_pmu_event(pe, data->metricgroups, data->filter,
-                                           data->raw, data->details, data->groups,
-                                           data->metriclist);
+       return metricgroup__add_to_mep_groups(pe, groups);
 }
 
-void metricgroup__print(bool metrics, bool metricgroups, char *filter,
-                       bool raw, bool details, const char *pmu_name)
+void metricgroup__print(const struct print_callbacks *print_cb, void *print_state)
 {
        struct rblist groups;
-       struct rb_node *node, *next;
-       struct strlist *metriclist = NULL;
        const struct pmu_events_table *table;
-
-       if (!metricgroups) {
-               metriclist = strlist__new(NULL, NULL);
-               if (!metriclist)
-                       return;
-       }
+       struct rb_node *node, *next;
 
        rblist__init(&groups);
        groups.node_new = mep_new;
        groups.node_delete = mep_delete;
        table = pmu_events_table__find();
        if (table) {
-               struct metricgroup_print_data data = {
-                       .pmu_name = pmu_name,
-                       .metriclist = metriclist,
-                       .metricgroups = metricgroups,
-                       .filter = filter,
-                       .raw = raw,
-                       .details = details,
-                       .groups = &groups,
-               };
-
                pmu_events_table_for_each_event(table,
-                                               metricgroup__print_callback,
-                                               &data);
+                                               metricgroup__add_to_mep_groups_callback,
+                                               &groups);
        }
        {
                struct metricgroup_iter_data data = {
-                       .fn = metricgroup__print_sys_event_iter,
-                       .data = (void *) &(struct metricgroup_print_sys_idata){
-                               .metriclist = metriclist,
-                               .metricgroups = metricgroups,
-                               .filter = filter,
-                               .raw = raw,
-                               .details = details,
-                               .groups = &groups,
-                       },
+                       .fn = metricgroup__add_to_mep_groups_callback,
+                       .data = &groups,
                };
-
                pmu_for_each_sys_event(metricgroup__sys_event_iter, &data);
        }
 
-       if (!filter || !rblist__empty(&groups)) {
-               if (metricgroups && !raw)
-                       printf("\nMetric Groups:\n\n");
-               else if (metrics && !raw)
-                       printf("\nMetrics:\n\n");
-       }
-
        for (node = rb_first_cached(&groups.entries); node; node = next) {
                struct mep *me = container_of(node, struct mep, nd);
 
-               if (metricgroups)
-                       printf("%s%s%s", me->name, metrics && !raw ? ":" : "", raw ? " " : "\n");
-               if (metrics)
-                       metricgroup__print_strlist(me->metrics, raw);
+               print_cb->print_metric(print_state,
+                               me->metric_group,
+                               me->metric_name,
+                               me->metric_desc,
+                               me->metric_long_desc,
+                               me->metric_expr,
+                               me->metric_unit);
                next = rb_next(node);
                rblist__remove_node(&groups, node);
        }
-       if (!metricgroups)
-               metricgroup__print_strlist(metriclist, raw);
-       strlist__delete(metriclist);
 }
 
 static const char *code_characters = ",-=@";
 
 struct evlist;
 struct evsel;
 struct option;
+struct print_callbacks;
 struct rblist;
 struct cgroup;
 
                                   bool metric_no_merge,
                                   struct rblist *metric_events);
 
-void metricgroup__print(bool metrics, bool groups, char *filter,
-                       bool raw, bool details, const char *pmu_name);
+void metricgroup__print(const struct print_callbacks *print_cb, void *print_state);
 bool metricgroup__has_metric(const char *metric);
 int arch_get_runtimeparam(const struct pmu_event *pe __maybe_unused);
 void metricgroup__rblist_exit(struct rblist *metric_events);
 
 #include "evsel.h"
 #include "pmu.h"
 #include "parse-events.h"
+#include "print-events.h"
 #include "header.h"
 #include "string2.h"
 #include "strbuf.h"
        return buf;
 }
 
-static char *format_alias_or(char *buf, int len, const struct perf_pmu *pmu,
-                            const struct perf_pmu_alias *alias)
-{
-       snprintf(buf, len, "%s OR %s/%s/", alias->name, pmu->name, alias->name);
-       return buf;
-}
-
 /** Struct for ordering events as output in perf list. */
 struct sevent {
        /** PMU for event. */
 
        /* Order CPU core events to be first */
        if (as->is_cpu != bs->is_cpu)
-               return bs->is_cpu - as->is_cpu;
+               return as->is_cpu ? -1 : 1;
 
        /* Order by PMU name. */
        a_pmu_name = as->pmu->name ?: "";
        return strcmp(a_name, b_name);
 }
 
-static void wordwrap(char *s, int start, int max, int corr)
-{
-       int column = start;
-       int n;
-
-       while (*s) {
-               int wlen = strcspn(s, " \t");
-
-               if (column + wlen >= max && column > start) {
-                       printf("\n%*s", start, "");
-                       column = start + corr;
-               }
-               n = printf("%s%.*s", column > start ? " " : "", wlen, s);
-               if (n <= 0)
-                       break;
-               s += wlen;
-               column += n;
-               s = skip_spaces(s);
-       }
-}
-
 bool is_pmu_core(const char *name)
 {
        return !strcmp(name, "cpu") || is_arm_pmu_core(name);
        return strcmp(a_pmu_name, b_pmu_name) == 0;
 }
 
-void print_pmu_events(const char *event_glob, bool name_only, bool quiet_flag,
-                       bool long_desc, bool details_flag, bool deprecated,
-                       const char *pmu_name)
+void print_pmu_events(const struct print_callbacks *print_cb, void *print_state)
 {
        struct perf_pmu *pmu;
-       struct perf_pmu_alias *alias;
+       struct perf_pmu_alias *event;
        char buf[1024];
        int printed = 0;
        int len, j;
        struct sevent *aliases;
-       int numdesc = 0;
-       int columns = pager_get_columns();
-       char *topic = NULL;
 
        pmu = NULL;
        len = 0;
        while ((pmu = perf_pmu__scan(pmu)) != NULL) {
-               list_for_each_entry(alias, &pmu->aliases, list)
+               list_for_each_entry(event, &pmu->aliases, list)
                        len++;
                if (pmu->selectable)
                        len++;
        pmu = NULL;
        j = 0;
        while ((pmu = perf_pmu__scan(pmu)) != NULL) {
-               bool is_cpu;
+               bool is_cpu = is_pmu_core(pmu->name) || perf_pmu__is_hybrid(pmu->name);
 
-               if (pmu_name && pmu->name && strcmp(pmu_name, pmu->name))
-                       continue;
-
-               is_cpu = is_pmu_core(pmu->name) || perf_pmu__is_hybrid(pmu->name);
-
-               list_for_each_entry(alias, &pmu->aliases, list) {
-                       if (alias->deprecated && !deprecated)
-                               continue;
-
-                       if (event_glob != NULL &&
-                           !(strglobmatch_nocase(alias->name, event_glob) ||
-                             (!is_cpu &&
-                              strglobmatch_nocase(alias->name, event_glob)) ||
-                             (alias->topic &&
-                              strglobmatch_nocase(alias->topic, event_glob))))
-                               continue;
-
-                       aliases[j].event = alias;
+               list_for_each_entry(event, &pmu->aliases, list) {
+                       aliases[j].event = event;
                        aliases[j].pmu = pmu;
                        aliases[j].is_cpu = is_cpu;
                        j++;
                }
-               if (pmu->selectable &&
-                   (event_glob == NULL || strglobmatch(pmu->name, event_glob))) {
+               if (pmu->selectable) {
                        aliases[j].event = NULL;
                        aliases[j].pmu = pmu;
                        aliases[j].is_cpu = is_cpu;
        len = j;
        qsort(aliases, len, sizeof(struct sevent), cmp_sevent);
        for (j = 0; j < len; j++) {
-               char *name, *desc;
+               const char *name, *alias = NULL, *scale_unit = NULL,
+                       *desc = NULL, *long_desc = NULL,
+                       *encoding_desc = NULL, *topic = NULL,
+                       *metric_name = NULL, *metric_expr = NULL;
+               bool deprecated = false;
+               size_t buf_used;
 
                /* Skip duplicates */
                if (j > 0 && pmu_alias_is_duplicate(&aliases[j], &aliases[j - 1]))
 
                if (!aliases[j].event) {
                        /* A selectable event. */
-                       snprintf(buf, sizeof(buf), "%s//", aliases[j].pmu->name);
+                       buf_used = snprintf(buf, sizeof(buf), "%s//", aliases[j].pmu->name) + 1;
                        name = buf;
-               } else if (aliases[j].event->desc) {
-                       name = aliases[j].event->name;
                } else {
-                       if (!name_only && aliases[j].is_cpu) {
-                               name = format_alias_or(buf, sizeof(buf), aliases[j].pmu,
-                                                      aliases[j].event);
+                       if (aliases[j].event->desc) {
+                               name = aliases[j].event->name;
+                               buf_used = 0;
                        } else {
                                name = format_alias(buf, sizeof(buf), aliases[j].pmu,
                                                    aliases[j].event);
+                               if (aliases[j].is_cpu) {
+                                       alias = name;
+                                       name = aliases[j].event->name;
+                               }
+                               buf_used = strlen(buf) + 1;
                        }
-               }
-               if (name_only) {
-                       printf("%s ", name);
-                       continue;
-               }
-               printed++;
-               if (!aliases[j].event || !aliases[j].event->desc || quiet_flag) {
-                       printf("  %-50s [Kernel PMU event]\n", name);
-                       continue;
-               }
-               if (numdesc++ == 0)
-                       printf("\n");
-               if (aliases[j].event->topic && (!topic ||
-                                               strcmp(topic, aliases[j].event->topic))) {
-                       printf("%s%s:\n", topic ? "\n" : "", aliases[j].event->topic);
+                       if (strlen(aliases[j].event->unit) || aliases[j].event->scale != 1.0) {
+                               scale_unit = buf + buf_used;
+                               buf_used += snprintf(buf + buf_used, sizeof(buf) - buf_used,
+                                               "%G%s", aliases[j].event->scale,
+                                               aliases[j].event->unit) + 1;
+                       }
+                       desc = aliases[j].event->desc;
+                       long_desc = aliases[j].event->long_desc;
                        topic = aliases[j].event->topic;
+                       encoding_desc = buf + buf_used;
+                       buf_used += snprintf(buf + buf_used, sizeof(buf) - buf_used,
+                                       "%s/%s/", aliases[j].pmu->name,
+                                       aliases[j].event->str) + 1;
+                       metric_name = aliases[j].event->metric_name;
+                       metric_expr = aliases[j].event->metric_expr;
+                       deprecated = aliases[j].event->deprecated;
                }
-               printf("  %-50s\n", name);
-               printf("%*s", 8, "[");
-               desc = long_desc ? aliases[j].event->long_desc : aliases[j].event->desc;
-               wordwrap(desc, 8, columns, 0);
-               printf("]\n");
-               if (details_flag) {
-                       printf("%*s%s/%s/ ", 8, "", aliases[j].pmu->name, aliases[j].event->str);
-                       if (aliases[j].event->metric_name)
-                               printf(" MetricName: %s", aliases[j].event->metric_name);
-                       if (aliases[j].event->metric_expr)
-                               printf(" MetricExpr: %s", aliases[j].event->metric_expr);
-                       putchar('\n');
-               }
+               print_cb->print_event(print_state,
+                               aliases[j].pmu->name,
+                               topic,
+                               name,
+                               alias,
+                               scale_unit,
+                               deprecated,
+                               "Kernel PMU event",
+                               desc,
+                               long_desc,
+                               encoding_desc,
+                               metric_name,
+                               metric_expr);
        }
        if (printed && pager_in_use())
                printf("\n");
 
 
 struct evsel_config_term;
 struct perf_cpu_map;
+struct print_callbacks;
 
 enum {
        PERF_PMU_FORMAT_VALUE_CONFIG,
 struct perf_pmu *perf_pmu__scan(struct perf_pmu *pmu);
 
 bool is_pmu_core(const char *name);
-void print_pmu_events(const char *event_glob, bool name_only, bool quiet,
-                     bool long_desc, bool details_flag,
-                     bool deprecated, const char *pmu_name);
+void print_pmu_events(const struct print_callbacks *print_cb, void *print_state);
 bool pmu_have_event(const char *pname, const char *name);
 
 int perf_pmu__scan_file(struct perf_pmu *pmu, const char *name, const char *fmt, ...) __scanf(3, 4);
 
 
 #define MAX_NAME_LEN 100
 
+/** Strings corresponding to enum perf_type_id. */
 static const char * const event_type_descriptors[] = {
        "Hardware event",
        "Software event",
 /*
  * Print the events from <debugfs_mount_point>/tracing/events
  */
-void print_tracepoint_events(const char *subsys_glob,
-                            const char *event_glob, bool name_only)
+void print_tracepoint_events(const struct print_callbacks *print_cb, void *print_state)
 {
        struct dirent **sys_namelist = NULL;
-       bool printed = false;
        int sys_items = tracing_events__scandir_alphasort(&sys_namelist);
 
        for (int i = 0; i < sys_items; i++) {
                    !strcmp(sys_dirent->d_name, ".."))
                        continue;
 
-               if (subsys_glob != NULL &&
-                   !strglobmatch(sys_dirent->d_name, subsys_glob))
-                       continue;
-
                dir_path = get_events_file(sys_dirent->d_name);
                if (!dir_path)
                        continue;
                        if (tp_event_has_id(dir_path, evt_dirent) != 0)
                                continue;
 
-                       if (event_glob != NULL &&
-                           !strglobmatch(evt_dirent->d_name, event_glob))
-                               continue;
-
                        snprintf(evt_path, MAXPATHLEN, "%s:%s",
                                 sys_dirent->d_name, evt_dirent->d_name);
-                       if (name_only)
-                               printf("%s ", evt_path);
-                       else {
-                               printf("  %-50s [%s]\n", evt_path,
-                                      event_type_descriptors[PERF_TYPE_TRACEPOINT]);
-                       }
-                       printed = true;
+                       print_cb->print_event(print_state,
+                                       /*topic=*/NULL,
+                                       /*pmu_name=*/NULL,
+                                       evt_path,
+                                       /*event_alias=*/NULL,
+                                       /*scale_unit=*/NULL,
+                                       /*deprecated=*/false,
+                                       "Tracepoint event",
+                                       /*desc=*/NULL,
+                                       /*long_desc=*/NULL,
+                                       /*encoding_desc=*/NULL,
+                                       /*metric_name=*/NULL,
+                                       /*metric_expr=*/NULL);
                }
                free(dir_path);
                free(evt_namelist);
        }
        free(sys_namelist);
-       if (printed && pager_in_use())
-               printf("\n");
 }
 
-void print_sdt_events(const char *subsys_glob, const char *event_glob,
-                     bool name_only)
+void print_sdt_events(const struct print_callbacks *print_cb, void *print_state)
 {
-       struct probe_cache *pcache;
-       struct probe_cache_entry *ent;
        struct strlist *bidlist, *sdtlist;
-       struct strlist_config cfg = {.dont_dupstr = true};
-       struct str_node *nd, *nd2;
-       char *buf, *path, *ptr = NULL;
-       bool show_detail = false;
-       int ret;
-
-       sdtlist = strlist__new(NULL, &cfg);
+       struct str_node *bid_nd, *sdt_name, *next_sdt_name;
+       const char *last_sdt_name = NULL;
+
+       /*
+        * The implicitly sorted sdtlist will hold the tracepoint name followed
+        * by @<buildid>. If the tracepoint name is unique (determined by
+        * looking at the adjacent nodes) the @<buildid> is dropped otherwise
+        * the executable path and buildid are added to the name.
+        */
+       sdtlist = strlist__new(NULL, NULL);
        if (!sdtlist) {
                pr_debug("Failed to allocate new strlist for SDT\n");
                return;
                pr_debug("Failed to get buildids: %d\n", errno);
                return;
        }
-       strlist__for_each_entry(nd, bidlist) {
-               pcache = probe_cache__new(nd->s, NULL);
+       strlist__for_each_entry(bid_nd, bidlist) {
+               struct probe_cache *pcache;
+               struct probe_cache_entry *ent;
+
+               pcache = probe_cache__new(bid_nd->s, NULL);
                if (!pcache)
                        continue;
                list_for_each_entry(ent, &pcache->entries, node) {
-                       if (!ent->sdt)
-                               continue;
-                       if (subsys_glob &&
-                           !strglobmatch(ent->pev.group, subsys_glob))
-                               continue;
-                       if (event_glob &&
-                           !strglobmatch(ent->pev.event, event_glob))
-                               continue;
-                       ret = asprintf(&buf, "%s:%s@%s", ent->pev.group,
-                                       ent->pev.event, nd->s);
-                       if (ret > 0)
-                               strlist__add(sdtlist, buf);
+                       char buf[1024];
+
+                       snprintf(buf, sizeof(buf), "%s:%s@%s",
+                                ent->pev.group, ent->pev.event, bid_nd->s);
+                       strlist__add(sdtlist, buf);
                }
                probe_cache__delete(pcache);
        }
        strlist__delete(bidlist);
 
-       strlist__for_each_entry(nd, sdtlist) {
-               buf = strchr(nd->s, '@');
-               if (buf)
-                       *(buf++) = '\0';
-               if (name_only) {
-                       printf("%s ", nd->s);
-                       continue;
-               }
-               nd2 = strlist__next(nd);
-               if (nd2) {
-                       ptr = strchr(nd2->s, '@');
-                       if (ptr)
-                               *ptr = '\0';
-                       if (strcmp(nd->s, nd2->s) == 0)
-                               show_detail = true;
+       strlist__for_each_entry(sdt_name, sdtlist) {
+               bool show_detail = false;
+               char *bid = strchr(sdt_name->s, '@');
+               char *evt_name = NULL;
+
+               if (bid)
+                       *(bid++) = '\0';
+
+               if (last_sdt_name && !strcmp(last_sdt_name, sdt_name->s)) {
+                       show_detail = true;
+               } else {
+                       next_sdt_name = strlist__next(sdt_name);
+                       if (next_sdt_name) {
+                               char *bid2 = strchr(next_sdt_name->s, '@');
+
+                               if (bid2)
+                                       *bid2 = '\0';
+                               if (strcmp(sdt_name->s, next_sdt_name->s) == 0)
+                                       show_detail = true;
+                               if (bid2)
+                                       *bid2 = '@';
+                       }
                }
+               last_sdt_name = sdt_name->s;
+
                if (show_detail) {
-                       path = build_id_cache__origname(buf);
-                       ret = asprintf(&buf, "%s@%s(%.12s)", nd->s, path, buf);
-                       if (ret > 0) {
-                               printf("  %-50s [%s]\n", buf, "SDT event");
-                               free(buf);
+                       char *path = build_id_cache__origname(bid);
+
+                       if (path) {
+                               if (asprintf(&evt_name, "%s@%s(%.12s)", sdt_name->s, path, bid) < 0)
+                                       evt_name = NULL;
+                               free(path);
                        }
-                       free(path);
-               } else
-                       printf("  %-50s [%s]\n", nd->s, "SDT event");
-               if (nd2) {
-                       if (strcmp(nd->s, nd2->s) != 0)
-                               show_detail = false;
-                       if (ptr)
-                               *ptr = '@';
                }
+               print_cb->print_event(print_state,
+                               /*topic=*/NULL,
+                               /*pmu_name=*/NULL,
+                               evt_name ?: sdt_name->s,
+                               /*event_alias=*/NULL,
+                               /*deprecated=*/false,
+                               /*scale_unit=*/NULL,
+                               "SDT event",
+                               /*desc=*/NULL,
+                               /*long_desc=*/NULL,
+                               /*encoding_desc=*/NULL,
+                               /*metric_name=*/NULL,
+                               /*metric_expr=*/NULL);
+
+               free(evt_name);
        }
        strlist__delete(sdtlist);
 }
 
-int print_hwcache_events(const char *event_glob, bool name_only)
+int print_hwcache_events(const struct print_callbacks *print_cb, void *print_state)
 {
        struct strlist *evt_name_list = strlist__new(NULL, NULL);
        struct str_node *nd;
                                char name[64];
 
                                __evsel__hw_cache_type_op_res_name(type, op, i, name, sizeof(name));
-                               if (event_glob != NULL && !strglobmatch(name, event_glob))
-                                       continue;
-
                                if (!perf_pmu__has_hybrid()) {
                                        if (is_event_supported(PERF_TYPE_HW_CACHE,
                                                               type | (op << 8) | (i << 16)))
        }
 
        strlist__for_each_entry(nd, evt_name_list) {
-               if (name_only) {
-                       printf("%s ", nd->s);
-                       continue;
-               }
-               printf("  %-50s [%s]\n", nd->s, event_type_descriptors[PERF_TYPE_HW_CACHE]);
+               print_cb->print_event(print_state,
+                               "cache",
+                               /*pmu_name=*/NULL,
+                               nd->s,
+                               /*event_alias=*/NULL,
+                               /*scale_unit=*/NULL,
+                               /*deprecated=*/false,
+                               event_type_descriptors[PERF_TYPE_HW_CACHE],
+                               /*desc=*/NULL,
+                               /*long_desc=*/NULL,
+                               /*encoding_desc=*/NULL,
+                               /*metric_name=*/NULL,
+                               /*metric_expr=*/NULL);
        }
-       if (!strlist__empty(evt_name_list) && pager_in_use())
-               printf("\n");
-
        strlist__delete(evt_name_list);
        return 0;
 }
 
-static void print_tool_event(const struct event_symbol *syms, const char *event_glob,
-                            bool name_only)
-{
-       if (syms->symbol == NULL)
-               return;
-
-       if (event_glob && !(strglobmatch(syms->symbol, event_glob) ||
-             (syms->alias && strglobmatch(syms->alias, event_glob))))
-               return;
-
-       if (name_only)
-               printf("%s ", syms->symbol);
-       else {
-               char name[MAX_NAME_LEN];
-
-               if (syms->alias && strlen(syms->alias))
-                       snprintf(name, MAX_NAME_LEN, "%s OR %s", syms->symbol, syms->alias);
-               else
-                       strlcpy(name, syms->symbol, MAX_NAME_LEN);
-               printf("  %-50s [%s]\n", name, "Tool event");
-       }
-}
-
-void print_tool_events(const char *event_glob, bool name_only)
+void print_tool_events(const struct print_callbacks *print_cb, void *print_state)
 {
        // Start at 1 because the first enum entry means no tool event.
-       for (int i = 1; i < PERF_TOOL_MAX; ++i)
-               print_tool_event(event_symbols_tool + i, event_glob, name_only);
-
-       if (pager_in_use())
-               printf("\n");
+       for (int i = 1; i < PERF_TOOL_MAX; ++i) {
+               print_cb->print_event(print_state,
+                               "tool",
+                               /*pmu_name=*/NULL,
+                               event_symbols_tool[i].symbol,
+                               event_symbols_tool[i].alias,
+                               /*scale_unit=*/NULL,
+                               /*deprecated=*/false,
+                               "Tool event",
+                               /*desc=*/NULL,
+                               /*long_desc=*/NULL,
+                               /*encoding_desc=*/NULL,
+                               /*metric_name=*/NULL,
+                               /*metric_expr=*/NULL);
+       }
 }
 
-void print_symbol_events(const char *event_glob, unsigned int type,
-                        struct event_symbol *syms, unsigned int max,
-                        bool name_only)
+void print_symbol_events(const struct print_callbacks *print_cb, void *print_state,
+                        unsigned int type, const struct event_symbol *syms,
+                        unsigned int max)
 {
        struct strlist *evt_name_list = strlist__new(NULL, NULL);
        struct str_node *nd;
                if (syms[i].symbol == NULL)
                        continue;
 
-               if (event_glob != NULL && !(strglobmatch(syms[i].symbol, event_glob) ||
-                     (syms[i].alias && strglobmatch(syms[i].alias, event_glob))))
-                       continue;
-
                if (!is_event_supported(type, i))
                        continue;
 
        }
 
        strlist__for_each_entry(nd, evt_name_list) {
-               if (name_only) {
-                       printf("%s ", nd->s);
-                       continue;
+               char *alias = strstr(nd->s, " OR ");
+
+               if (alias) {
+                       *alias = '\0';
+                       alias += 4;
                }
-               printf("  %-50s [%s]\n", nd->s, event_type_descriptors[type]);
+               print_cb->print_event(print_state,
+                               /*topic=*/NULL,
+                               /*pmu_name=*/NULL,
+                               nd->s,
+                               alias,
+                               /*scale_unit=*/NULL,
+                               /*deprecated=*/false,
+                               event_type_descriptors[type],
+                               /*desc=*/NULL,
+                               /*long_desc=*/NULL,
+                               /*encoding_desc=*/NULL,
+                               /*metric_name=*/NULL,
+                               /*metric_expr=*/NULL);
        }
-       if (!strlist__empty(evt_name_list) && pager_in_use())
-               printf("\n");
-
        strlist__delete(evt_name_list);
 }
 
 /*
  * Print the help text for the event symbols:
  */
-void print_events(const char *event_glob, bool name_only, bool quiet_flag,
-                       bool long_desc, bool details_flag, bool deprecated,
-                       const char *pmu_name)
+void print_events(const struct print_callbacks *print_cb, void *print_state)
 {
-       print_symbol_events(event_glob, PERF_TYPE_HARDWARE,
-                           event_symbols_hw, PERF_COUNT_HW_MAX, name_only);
-
-       print_symbol_events(event_glob, PERF_TYPE_SOFTWARE,
-                           event_symbols_sw, PERF_COUNT_SW_MAX, name_only);
-       print_tool_events(event_glob, name_only);
-
-       print_hwcache_events(event_glob, name_only);
-
-       print_pmu_events(event_glob, name_only, quiet_flag, long_desc,
-                       details_flag, deprecated, pmu_name);
-
-       if (event_glob != NULL)
-               return;
-
-       if (!name_only) {
-               printf("  %-50s [%s]\n",
-                      "rNNN",
-                      event_type_descriptors[PERF_TYPE_RAW]);
-               printf("  %-50s [%s]\n",
-                      "cpu/t1=v1[,t2=v2,t3 ...]/modifier",
-                      event_type_descriptors[PERF_TYPE_RAW]);
-               if (pager_in_use())
-                       printf("   (see 'man perf-list' on how to encode it)\n\n");
-
-               printf("  %-50s [%s]\n",
-                      "mem:<addr>[/len][:access]",
-                       event_type_descriptors[PERF_TYPE_BREAKPOINT]);
-               if (pager_in_use())
-                       printf("\n");
-       }
-
-       print_tracepoint_events(NULL, NULL, name_only);
-
-       print_sdt_events(NULL, NULL, name_only);
-
-       metricgroup__print(true, true, NULL, name_only, details_flag,
-                          pmu_name);
-
-       print_libpfm_events(name_only, long_desc);
+       print_symbol_events(print_cb, print_state, PERF_TYPE_HARDWARE,
+                       event_symbols_hw, PERF_COUNT_HW_MAX);
+       print_symbol_events(print_cb, print_state, PERF_TYPE_SOFTWARE,
+                       event_symbols_sw, PERF_COUNT_SW_MAX);
+
+       print_tool_events(print_cb, print_state);
+
+       print_hwcache_events(print_cb, print_state);
+
+       print_pmu_events(print_cb, print_state);
+
+       print_cb->print_event(print_state,
+                       /*topic=*/NULL,
+                       /*pmu_name=*/NULL,
+                       "rNNN",
+                       /*event_alias=*/NULL,
+                       /*scale_unit=*/NULL,
+                       /*deprecated=*/false,
+                       event_type_descriptors[PERF_TYPE_RAW],
+                       /*desc=*/NULL,
+                       /*long_desc=*/NULL,
+                       /*encoding_desc=*/NULL,
+                       /*metric_name=*/NULL,
+                       /*metric_expr=*/NULL);
+
+       print_cb->print_event(print_state,
+                       /*topic=*/NULL,
+                       /*pmu_name=*/NULL,
+                       "cpu/t1=v1[,t2=v2,t3 ...]/modifier",
+                       /*event_alias=*/NULL,
+                       /*scale_unit=*/NULL,
+                       /*deprecated=*/false,
+                       event_type_descriptors[PERF_TYPE_RAW],
+                       "(see 'man perf-list' on how to encode it)",
+                       /*long_desc=*/NULL,
+                       /*encoding_desc=*/NULL,
+                       /*metric_name=*/NULL,
+                       /*metric_expr=*/NULL);
+
+       print_cb->print_event(print_state,
+                       /*topic=*/NULL,
+                       /*pmu_name=*/NULL,
+                       "mem:<addr>[/len][:access]",
+                       /*scale_unit=*/NULL,
+                       /*event_alias=*/NULL,
+                       /*deprecated=*/false,
+                       event_type_descriptors[PERF_TYPE_BREAKPOINT],
+                       /*desc=*/NULL,
+                       /*long_desc=*/NULL,
+                       /*encoding_desc=*/NULL,
+                       /*metric_name=*/NULL,
+                       /*metric_expr=*/NULL);
+
+       print_tracepoint_events(print_cb, print_state);
+
+       print_sdt_events(print_cb, print_state);
+
+       metricgroup__print(print_cb, print_state);
+
+       print_libpfm_events(print_cb, print_state);
 }
 
 #ifndef __PERF_PRINT_EVENTS_H
 #define __PERF_PRINT_EVENTS_H
 
+#include <linux/perf_event.h>
 #include <stdbool.h>
 
 struct event_symbol;
 
-void print_events(const char *event_glob, bool name_only, bool quiet_flag,
-                 bool long_desc, bool details_flag, bool deprecated,
-                 const char *pmu_name);
-int print_hwcache_events(const char *event_glob, bool name_only);
-void print_sdt_events(const char *subsys_glob, const char *event_glob,
-                     bool name_only);
-void print_symbol_events(const char *event_glob, unsigned int type,
-                        struct event_symbol *syms, unsigned int max,
-                        bool name_only);
-void print_tool_events(const char *event_glob, bool name_only);
-void print_tracepoint_events(const char *subsys_glob, const char *event_glob,
-                            bool name_only);
+struct print_callbacks {
+       void (*print_start)(void *print_state);
+       void (*print_end)(void *print_state);
+       void (*print_event)(void *print_state, const char *topic,
+                       const char *pmu_name,
+                       const char *event_name, const char *event_alias,
+                       const char *scale_unit,
+                       bool deprecated, const char *event_type_desc,
+                       const char *desc, const char *long_desc,
+                       const char *encoding_desc,
+                       const char *metric_name, const char *metric_expr);
+       void (*print_metric)(void *print_state,
+                       const char *group,
+                       const char *name,
+                       const char *desc,
+                       const char *long_desc,
+                       const char *expr,
+                       const char *unit);
+};
+
+/** Print all events, the default when no options are specified. */
+void print_events(const struct print_callbacks *print_cb, void *print_state);
+int print_hwcache_events(const struct print_callbacks *print_cb, void *print_state);
+void print_sdt_events(const struct print_callbacks *print_cb, void *print_state);
+void print_symbol_events(const struct print_callbacks *print_cb, void *print_state,
+                        unsigned int type, const struct event_symbol *syms,
+                        unsigned int max);
+void print_tool_events(const struct print_callbacks *print_cb, void *print_state);
+void print_tracepoint_events(const struct print_callbacks *print_cb, void *print_state);
 
 #endif /* __PERF_PRINT_EVENTS_H */