perf: Use libpfm4 and Linux style event strings
authorBarret Rhoden <brho@cs.berkeley.edu>
Thu, 19 May 2016 20:04:18 +0000 (16:04 -0400)
committerBarret Rhoden <brho@cs.berkeley.edu>
Thu, 16 Jun 2016 15:48:36 +0000 (11:48 -0400)
We had been using a custom event string, which was incompatible with both
Linux's perf and libpfm4.

Now, we accept any libpfm4 string, and barring that, we'll use perf-style
raw codes.  We accept pfm4-like modifiers, such as :u and :k.  Unlike perf,
we require that each modifier/token is separated by :.

So this works:

-e inst_retired:u:k

But this doesn't:
-e inst_retired:uk

That's actually libpfm4, not us.  I do the same thing for the raw codes
though, since we might as well be consistent (and simple).

In the process, I also fixed a couple issues with event specification.

Many events from libpfm4 are more than 8 bits long.  The old code assumed
that the event was 8 bits and the mask was 8 bits.  That meant that we
could submit events like 0x13c properly (you'd have to write it as
0x3c:0x01, even though perf list told your 0x13c).  Surprise!  Now we allow
longer event selectors, where the upper bits are the mask.  That's just
what libpfm4 expects, and really it is how the hardware (and OS interface)
are programmed.

The other issue is pseudo-encoded events.  On most Intel machines, libpfm4
returns the event 0x300 for unhalted_reference_cycles.  That's not a real
code, and will silently fail.  It needs to be the fixed counter 2.  Linux
knows this, but we weren't interpreting it.  Without these changes,
attempts to use unhalted_reference_cycles would simply return 0 for the
perf counters.

Signed-off-by: Barret Rhoden <brho@cs.berkeley.edu>
tools/profile/perf/perf_core.c
tools/profile/perf/perf_core.h

index ee3d3ea..1392e7e 100644 (file)
@@ -1,7 +1,9 @@
-/* Copyright (c) 2015 Google Inc
+/* Copyright (c) 2015-2016 Google Inc
  * Davide Libenzi <dlibenzi@google.com>
- * See LICENSE for details.
- */
+ * Barret Rhoden <brho@cs.berkeley.edu>
+ * Stephane Eranian <eranian@gmail.com> (perf_show_event_info() from libpfm4)
+ *
+ * See LICENSE for details. */
 
 #include <ros/arch/msr-index.h>
 #include <ros/arch/perfmon.h>
@@ -31,31 +33,6 @@ struct event_coords {
        const char *umask;
 };
 
-static const uint32_t invalid_mask = (uint32_t) -1;
-
-static void perf_parse_event_coords(const char *name, struct event_coords *ec)
-{
-       const char *cptr = strchr(name, ':');
-
-       if (cptr == NULL) {
-               ec->buffer = NULL;
-               ec->event = name;
-               ec->umask = NULL;
-       } else {
-               size_t cpos = cptr - name;
-
-               ec->buffer = xstrdup(name);
-               ec->event = ec->buffer;
-               ec->umask = ec->buffer + cpos + 1;
-               ec->buffer[cpos] = 0;
-       }
-}
-
-static void perf_free_event_coords(struct event_coords *ec)
-{
-       free(ec->buffer);
-}
-
 static const char *perf_get_event_mask_name(const pfm_event_info_t *einfo,
                                                                                        uint32_t mask)
 {
@@ -82,100 +59,6 @@ static const char *perf_get_event_mask_name(const pfm_event_info_t *einfo,
        return NULL;
 }
 
-static int perf_resolve_event_name(const char *name, uint32_t *event,
-                                                                  uint32_t *mask, uint32_t mask_hint)
-{
-       int idx;
-       struct event_coords ec;
-
-       perf_parse_event_coords(name, &ec);
-
-       idx = pfm_find_event(ec.event);
-       if (idx >= 0) {
-               int i;
-               pfm_err_t err;
-               pfm_event_info_t einfo;
-               pfm_event_attr_info_t ainfo;
-
-               ZERO_DATA(einfo);
-               einfo.size = sizeof(einfo);
-               err = pfm_get_event_info(idx, PFM_OS_NONE, &einfo);
-               if (err != PFM_SUCCESS) {
-                       fprintf(stderr, "Unable to retrieve event (%s) info: %s\n",
-                                       name, pfm_strerror(err));
-                       exit(1);
-               }
-
-               *event = (uint32_t) einfo.code;
-               *mask = invalid_mask;
-
-               ZERO_DATA(ainfo);
-               ainfo.size = sizeof(ainfo);
-               pfm_for_each_event_attr(i, &einfo) {
-                       err = pfm_get_event_attr_info(idx, i, PFM_OS_NONE, &ainfo);
-                       if (err != PFM_SUCCESS) {
-                               fprintf(stderr, "Failed to get attribute info: %s\n",
-                                               pfm_strerror(err));
-                               exit(1);
-                       }
-                       if (ainfo.type == PFM_ATTR_UMASK) {
-                               if (mask_hint != invalid_mask) {
-                                       if (mask_hint == (uint32_t) ainfo.code) {
-                                               *mask = (uint32_t) ainfo.code;
-                                               break;
-                                       }
-                               } else if (!ec.umask) {
-                                       *mask = (uint32_t) ainfo.code;
-                                       if (ainfo.is_dfl)
-                                               break;
-                               } else if (!strcmp(ec.umask, ainfo.name)) {
-                                       *mask = (uint32_t) ainfo.code;
-                                       break;
-                               }
-                       }
-               }
-       }
-       perf_free_event_coords(&ec);
-
-       return idx;
-}
-
-static int perf_find_event_by_id(uint32_t event, uint32_t mask)
-{
-       int pmu;
-       pfm_pmu_info_t pinfo;
-       pfm_event_info_t info;
-
-    ZERO_DATA(pinfo);
-    pinfo.size = sizeof(pinfo);
-    ZERO_DATA(info);
-    info.size = sizeof(info);
-
-       pfm_for_all_pmus(pmu) {
-               pfm_err_t err = pfm_get_pmu_info(pmu, &pinfo);
-
-               if (err != PFM_SUCCESS || !pinfo.is_present)
-                       continue;
-
-               for (int i = pinfo.first_event; i != -1; i = pfm_get_event_next(i)) {
-                       uint32_t cevent, cmask;
-
-                       err = pfm_get_event_info(i, PFM_OS_NONE, &info);
-                       if (err != PFM_SUCCESS) {
-                               fprintf(stderr, "Failed to get event info: %s\n",
-                                               pfm_strerror(err));
-                               exit(1);
-                       }
-                       if (perf_resolve_event_name(info.name, &cevent, &cmask, mask) != i)
-                               continue;
-                       if ((cevent == event) && (cmask == mask))
-                               return i;
-               }
-       }
-
-       return -1;
-}
-
 void perf_initialize(int argc, char *argv[])
 {
        pfm_err_t err = pfm_initialize();
@@ -192,84 +75,183 @@ void perf_finalize(void)
        pfm_terminate();
 }
 
-void perf_parse_event(const char *str, struct perf_eventsel *sel)
+/* This is arch-specific and maybe model specific in the future.  For some
+ * events, pfm4 gives us a pseudo encoding.  Those codes don't map to real
+ * hardware events and are meant to be interpreted by Linux for *other* HW
+ * events, e.g. in arch/x86/events/intel/core.c.
+ *
+ * While we're here, we can also take *real* encodings and treat them like
+ * pseudo encodings.  For instance, the arch event 0x3c (unhalted_core_cycles)
+ * can also be done with fixed counter 1.  This all assumes we have version 2 or
+ * later of Intel's perfmon. */
+static void x86_handle_pseudo_encoding(struct perf_eventsel *sel)
 {
-       static const char *const event_spec =
-               "{EVENT_ID:MASK,EVENT_NAME:MASK_NAME}[,os[={0,1}]][,usr[={0,1}]]"
-               "[,int[={0,1}]][,invcmsk[={0,1}]][,cmask=MASK][,icount=COUNT]";
-       char *dstr = xstrdup(str), *sptr, *tok, *ev;
-
-       tok = strtok_r(dstr, ",", &sptr);
-       if (tok == NULL) {
-               fprintf(stderr, "Invalid event spec string: '%s'\n\t%s\n", str,
-                               event_spec);
-               exit(1);
+       uint8_t lower_byte;
+
+       switch (sel->ev.event & 0xffff) {
+       case 0xc0:      /* arch inst_retired */
+               sel->ev.flags |= PERFMON_FIXED_EVENT;
+               PMEV_SET_MASK(sel->ev.event, 0);
+               PMEV_SET_EVENT(sel->ev.event, 0);
+               return;
+       case 0x3c:      /* arch unhalted_core_cycles */
+               sel->ev.flags |= PERFMON_FIXED_EVENT;
+               PMEV_SET_MASK(sel->ev.event, 0);
+               PMEV_SET_EVENT(sel->ev.event, 1);
+               return;
+       case 0x13c:     /* arch unhalted_reference_cycles */
+       case 0x300:     /* pseudo encode: unhalted_reference_cycles */
+               sel->ev.flags |= PERFMON_FIXED_EVENT;
+               PMEV_SET_MASK(sel->ev.event, 0);
+               PMEV_SET_EVENT(sel->ev.event, 2);
+               return;
+       };
+       lower_byte = sel->ev.event & 0xff;
+       if ((lower_byte == 0x00) || (lower_byte == 0xff))
+               fprintf(stderr, "Unhandled pseudo encoding %d\n", lower_byte);
+}
+
+/* Parse the string using pfm's lookup functions.  Returns TRUE on success and
+ * fills in parts of sel. */
+static bool parse_os_encoding(const char *str, struct perf_eventsel *sel)
+{
+       pfm_pmu_encode_arg_t encode;
+       int err;
+       char *ptr;
+
+       memset(&encode, 0, sizeof(encode));
+       encode.size = sizeof(encode);
+       encode.fstr = &ptr;
+       err = pfm_get_os_event_encoding(str, PFM_PLM3 | PFM_PLM0, PFM_OS_NONE,
+                                       &encode);
+       if (err)
+               return FALSE;
+       strlcpy(sel->fq_str, ptr, MAX_FQSTR_SZ);
+       free(ptr);
+       if (encode.count == 0) {
+               fprintf(stderr, "Found event %s, but it had no codes!\n", sel->fq_str);
+               return FALSE;
        }
-       ZERO_DATA(*sel);
-       sel->eidx = -1;
-       sel->ev.flags = 0;
-       sel->ev.event = 0;
-       PMEV_SET_OS(sel->ev.event, 1);
-       PMEV_SET_USR(sel->ev.event, 1);
-       PMEV_SET_EN(sel->ev.event, 1);
-       if (isdigit(*tok)) {
-               ev = strchr(tok, ':');
-               if (ev == NULL) {
-                       fprintf(stderr, "Invalid event spec string: '%s'\n"
-                                       "\tShould be: %s\n", str, event_spec);
-                       exit(1);
-               }
-               *ev++ = 0;
-               PMEV_SET_EVENT(sel->ev.event, (uint8_t) strtoul(tok, NULL, 0));
-               PMEV_SET_MASK(sel->ev.event, (uint8_t) strtoul(ev, NULL, 0));
-       } else {
-               uint32_t event, mask;
+       sel->ev.event = encode.codes[0];
+       sel->eidx = encode.idx;
+       x86_handle_pseudo_encoding(sel);
+       return TRUE;
+}
 
-               sel->eidx = perf_resolve_event_name(tok, &event, &mask, invalid_mask);
-               if (sel->eidx < 0) {
-                       fprintf(stderr, "Unable to find event: %s\n", tok);
-                       exit(1);
-               }
-               PMEV_SET_EVENT(sel->ev.event, (uint8_t) event);
-               PMEV_SET_MASK(sel->ev.event, (uint8_t) mask);
+static bool is_end_of_raw(char c)
+{
+       return (c == ':') || (c == '\0');
+}
+
+/* Helper: given a string, if the event is a raw hex code, return its numeric
+ * value.  Returns -1 if it does not match a raw code.
+ *
+ * rNN[N][N][:,\0].  Begins with r, has at least two hexdigits, up to 4, and
+ * ends with : , or \0. */
+static int extract_raw_code(const char *event)
+{
+       int i;
+       char copy[5] = {0};
+
+       if (event[0] != 'r')
+               return -1;
+       event++;
+       for (i = 0; i < 4; i++) {
+               if (isxdigit(event[i]))
+                       continue;
+               if (is_end_of_raw(event[i]))
+                       break;
+               return -1;
        }
-       while ((tok = strtok_r(NULL, ",", &sptr)) != NULL) {
-               ev = strchr(tok, '=');
-               if (ev)
-                       *ev++ = 0;
-               if (!strcmp(tok, "os")) {
-                       PMEV_SET_OS(sel->ev.event, (ev == NULL || atoi(ev) != 0) ? 1 : 0);
-               } else if (!strcmp(tok, "usr")) {
-                       PMEV_SET_USR(sel->ev.event, (ev == NULL || atoi(ev) != 0) ? 1 : 0);
-               } else if (!strcmp(tok, "int")) {
-                       PMEV_SET_INTEN(sel->ev.event,
-                                                  (ev == NULL || atoi(ev) != 0) ? 1 : 0);
-               } else if (!strcmp(tok, "invcmsk")) {
-                       PMEV_SET_INVCMSK(sel->ev.event,
-                                                        (ev == NULL || atoi(ev) != 0) ? 1 : 0);
-               } else if (!strcmp(tok, "cmask")) {
-                       if (ev == NULL) {
-                               fprintf(stderr, "Invalid event spec string: '%s'\n"
-                                               "\tShould be: %s\n", str, event_spec);
-                               exit(1);
-                       }
-                       PMEV_SET_CMASK(sel->ev.event, (uint32_t) strtoul(ev, NULL, 0));
-               } else if (!strcmp(tok, "icount")) {
-                       if (ev == NULL) {
-                               fprintf(stderr, "Invalid event spec string: '%s'\n"
-                                               "\tShould be: %s\n", str, event_spec);
-                               exit(1);
+       if (!is_end_of_raw(event[i]))
+               return -1;
+       /* 'i' tracks how many we found (i.e. every 'continue') */
+       if (i < 2)
+               return -1;
+       /* need a null-terminated raw code for strtol. */
+       for (int j = 0; j < i; j++)
+               copy[j] = event[j];
+       return strtol(copy, NULL, 16);
+}
+
+/* Parse the string for a raw encoding.  Returns TRUE on success and fills in
+ * parts of sel.  It has basic modifiers, like pfm4, for setting bits in the
+ * event code.  This is arch specific, and is all x86 (intel) for now. */
+static bool parse_raw_encoding(const char *str, struct perf_eventsel *sel)
+{
+       int code = extract_raw_code(str);
+       char *dup_str, *tok_save, *tok;
+
+       if (code == -1)
+               return FALSE;
+       sel->eidx = -1;
+       sel->ev.event = code;
+       strncpy(sel->fq_str, str, MAX_FQSTR_SZ);
+       dup_str = xstrdup(str);
+       tok = strtok_r(dup_str, ":", &tok_save);
+       assert(tok);    /* discard first token; it must exist */
+       while ((tok = strtok_r(NULL, ":", &tok_save))) {
+               switch (tok[0]) {
+               case 'u':
+                       PMEV_SET_USR(sel->ev.event, 1);
+                       break;
+               case 'k':
+                       PMEV_SET_OS(sel->ev.event, 1);
+                       break;
+               case 'e':
+                       PMEV_SET_EDGE(sel->ev.event, 1);
+                       break;
+               case 'p':
+                       PMEV_SET_PC(sel->ev.event, 1);
+                       break;
+               case 't':
+                       PMEV_SET_ANYTH(sel->ev.event, 1);
+                       break;
+               case 'i':
+                       PMEV_SET_INVCMSK(sel->ev.event, 1);
+                       break;
+               case 'c':
+                       if (tok[1] != '=') {
+                               fprintf(stderr, "Bad cmask tok %s, ignoring\n", tok);
+                               break;
                        }
-                       sel->ev.trigger_count = (uint64_t) strtoul(ev, NULL, 0);
+                       errno = 0;
+                       PMEV_SET_CMASK(sel->ev.event, strtoul(&tok[2], NULL, 0));
+                       if (errno)
+                               fprintf(stderr, "Bad cmask tok %s, trying anyway\n", tok);
+                       break;
                }
        }
-       if (PMEV_GET_INTEN(sel->ev.event) && !sel->ev.trigger_count) {
-               fprintf(stderr,
-                               "Counter trigger count for interrupt is too small: %lu\n",
-                               sel->ev.trigger_count);
-               exit(1);
+       free(dup_str);
+       /* Note that we do not call x86_handle_pseudo_encoding here.  We'll submit
+        * exactly what the user asked us for - which also means no fixed counters
+        * for them (unless we want a :f: token or something). */
+       return TRUE;
+}
+
+/* Given an event description string, fills out sel with the info from the
+ * string such that it can be submitted to the OS.
+ *
+ * The caller can set more bits if they like, such as whether or not to
+ * interrupt on overflow, the sample_period, etc.  None of those settings are
+ * part of the event string.
+ *
+ * Kills the program on failure. */
+void perf_parse_event(const char *str, struct perf_eventsel *sel)
+{
+       ZERO_DATA(*sel);
+       if (parse_os_encoding(str, sel))
+               goto success;
+       if (parse_raw_encoding(str, sel))
+               goto success;
+       fprintf(stderr, "Failed to parse event string %s\n", str);
+       exit(-1);
+success:
+       if (!PMEV_GET_OS(sel->ev.event) && !PMEV_GET_USR(sel->ev.event)) {
+               PMEV_SET_OS(sel->ev.event, 1);
+               PMEV_SET_USR(sel->ev.event, 1);
        }
-       free(dstr);
+       PMEV_SET_EN(sel->ev.event, 1);
 }
 
 static void perf_get_arch_info(int perf_fd, struct perf_arch_info *pai)
@@ -490,6 +472,7 @@ static void perf_print_attr_flags(const pfm_event_attr_info_t *info, FILE *file)
                fputs("None ", file);
 }
 
+/* Ported from libpfm4 */
 static void perf_show_event_info(const pfm_event_info_t *info,
                                                                 const pfm_pmu_info_t *pinfo, FILE *file)
 {
@@ -633,15 +616,6 @@ void perf_get_event_string(const struct perf_eventsel *sel, char *sbuf,
        }
 }
 
-void perf_make_eventsel_from_event_mask(struct perf_eventsel *sel,
-                                                                               uint32_t event, uint32_t mask)
-{
-       ZERO_DATA(*sel);
-       PMEV_SET_EVENT(sel->ev.event, (uint8_t) event);
-       PMEV_SET_MASK(sel->ev.event, (uint8_t) mask);
-       sel->eidx = perf_find_event_by_id(event, mask);
-}
-
 void perf_convert_trace_data(struct perfconv_context *cctx, const char *input,
                                                         const char *output)
 {
index d305f37..5cb93ea 100644 (file)
@@ -26,9 +26,11 @@ struct perf_arch_info {
        uint32_t fix_counters_x_proc;
 };
 
+#define MAX_FQSTR_SZ 128
 struct perf_eventsel {
        struct perfmon_event ev;
        int eidx;
+       char fq_str[MAX_FQSTR_SZ];
 };
 
 struct perf_event {
@@ -63,8 +65,6 @@ void perf_context_show_values(struct perf_context *pctx, FILE *file);
 void perf_show_events(const char *rx, FILE *file);
 void perf_get_event_string(const struct perf_eventsel *sel, char *sbuf,
                                                   size_t size);
-void perf_make_eventsel_from_event_mask(struct perf_eventsel *sel,
-                                                                               uint32_t event, uint32_t mask);
 void perf_convert_trace_data(struct perfconv_context *cctx, const char *input,
                                                         const char *output);