| /* Copyright (c) 2015-2016 Google Inc |
| * Davide Libenzi <dlibenzi@google.com> |
| * 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> |
| #include <ros/common.h> |
| #include <ros/memops.h> |
| #include <sys/types.h> |
| #include <stdint.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <ctype.h> |
| #include <limits.h> |
| #include <errno.h> |
| #include <regex.h> |
| #include <parlib/parlib.h> |
| #include <parlib/core_set.h> |
| #include <perfmon/err.h> |
| #include <perfmon/pfmlib.h> |
| #include "xlib.h" |
| #include "perfconv.h" |
| #include "perf_core.h" |
| #include "elf.h" |
| |
| struct perf_generic_event { |
| char *name; |
| uint32_t type; |
| uint32_t config; |
| }; |
| |
| struct perf_generic_event generic_events[] = { |
| { .name = "cycles", |
| .type = PERF_TYPE_HARDWARE, |
| .config = PERF_COUNT_HW_CPU_CYCLES, |
| }, |
| { .name = "cpu-cycles", |
| .type = PERF_TYPE_HARDWARE, |
| .config = PERF_COUNT_HW_CPU_CYCLES, |
| }, |
| { .name = "instructions", |
| .type = PERF_TYPE_HARDWARE, |
| .config = PERF_COUNT_HW_INSTRUCTIONS, |
| }, |
| { .name = "cache-references", |
| .type = PERF_TYPE_HARDWARE, |
| .config = PERF_COUNT_HW_CACHE_REFERENCES, |
| }, |
| { .name = "cache-misses", |
| .type = PERF_TYPE_HARDWARE, |
| .config = PERF_COUNT_HW_CACHE_MISSES, |
| }, |
| { .name = "branches", |
| .type = PERF_TYPE_HARDWARE, |
| .config = PERF_COUNT_HW_BRANCH_INSTRUCTIONS, |
| }, |
| { .name = "branch-instructions", |
| .type = PERF_TYPE_HARDWARE, |
| .config = PERF_COUNT_HW_BRANCH_INSTRUCTIONS, |
| }, |
| { .name = "branch-misses", |
| .type = PERF_TYPE_HARDWARE, |
| .config = PERF_COUNT_HW_BRANCH_MISSES, |
| }, |
| { .name = "bus-cycles", |
| .type = PERF_TYPE_HARDWARE, |
| .config = PERF_COUNT_HW_BUS_CYCLES, |
| }, |
| }; |
| |
| static const char *perf_get_event_mask_name(const pfm_event_info_t *einfo, |
| uint32_t mask) |
| { |
| int i; |
| pfm_event_attr_info_t ainfo; |
| |
| ZERO_DATA(ainfo); |
| ainfo.size = sizeof(ainfo); |
| pfm_for_each_event_attr(i, einfo) { |
| pfm_err_t err = pfm_get_event_attr_info(einfo->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 == (uint32_t) ainfo.code) |
| return ainfo.name; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| void perf_initialize(void) |
| { |
| pfm_err_t err = pfm_initialize(); |
| |
| if (err != PFM_SUCCESS) { |
| fprintf(stderr, "Unable to initialize perfmon library: %s\n", |
| pfm_strerror(err)); |
| exit(1); |
| } |
| symbol__elf_init(); |
| } |
| |
| void perf_finalize(void) |
| { |
| pfm_terminate(); |
| } |
| |
| /* 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) |
| { |
| 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_pfm_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; |
| } |
| sel->ev.event = encode.codes[0]; |
| x86_handle_pseudo_encoding(sel); |
| sel->type = PERF_TYPE_RAW; |
| sel->config = (PMEV_GET_MASK(sel->ev.event) << 8) | |
| PMEV_GET_EVENT(sel->ev.event); |
| return TRUE; |
| } |
| |
| 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; |
| } |
| 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); |
| } |
| |
| /* Takes any modifiers, e.g. u:k:etc, and sets the respective values in sel. */ |
| static void parse_modifiers(const char *str, struct perf_eventsel *sel) |
| { |
| char *dup_str, *tok, *tok_save = 0; |
| |
| dup_str = xstrdup(str); |
| for (tok = strtok_r(dup_str, ":", &tok_save); |
| tok; |
| 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; |
| } |
| 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; |
| } |
| } |
| free(dup_str); |
| } |
| |
| /* 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 *colon; |
| |
| if (code == -1) |
| return FALSE; |
| sel->ev.event = code; |
| strlcpy(sel->fq_str, str, MAX_FQSTR_SZ); |
| colon = strchr(str, ':'); |
| if (colon) |
| parse_modifiers(colon + 1, sel); |
| /* 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). */ |
| sel->type = PERF_TYPE_RAW; |
| sel->config = (PMEV_GET_MASK(sel->ev.event) << 8) | |
| PMEV_GET_EVENT(sel->ev.event); |
| return TRUE; |
| } |
| |
| /* Helper, returns true is str is a generic event string, and fills in sel with |
| * the type and config. */ |
| static bool generic_str_get_code(const char *str, struct perf_eventsel *sel) |
| { |
| char *colon = strchr(str, ':'); |
| /* if there was no :, we compare as far as we can. generic_events.name |
| * is a string literal, so strcmp() is fine. */ |
| size_t len = colon ? colon - str : SIZE_MAX; |
| |
| for (int i = 0; i < COUNT_OF(generic_events); i++) { |
| if (!strncmp(generic_events[i].name, str, len)) { |
| sel->type = generic_events[i].type; |
| sel->config = generic_events[i].config; |
| return TRUE; |
| } |
| } |
| return FALSE; |
| } |
| |
| /* TODO: this is arch-specific and possibly machine-specific. (intel for now). |
| * Basically a lot of our perf is arch-dependent. (e.g. PMEV_*). */ |
| static bool arch_translate_generic(struct perf_eventsel *sel) |
| { |
| switch (sel->type) { |
| case PERF_TYPE_HARDWARE: |
| /* These are the intel/x86 architectural perf events */ |
| switch (sel->config) { |
| case PERF_COUNT_HW_CPU_CYCLES: |
| PMEV_SET_MASK(sel->ev.event, 0x00); |
| PMEV_SET_EVENT(sel->ev.event, 0x3c); |
| break; |
| case PERF_COUNT_HW_INSTRUCTIONS: |
| PMEV_SET_MASK(sel->ev.event, 0x00); |
| PMEV_SET_EVENT(sel->ev.event, 0xc0); |
| break; |
| case PERF_COUNT_HW_CACHE_REFERENCES: |
| PMEV_SET_MASK(sel->ev.event, 0x4f); |
| PMEV_SET_EVENT(sel->ev.event, 0x2e); |
| break; |
| case PERF_COUNT_HW_CACHE_MISSES: |
| PMEV_SET_MASK(sel->ev.event, 0x41); |
| PMEV_SET_EVENT(sel->ev.event, 0x2e); |
| break; |
| case PERF_COUNT_HW_BRANCH_INSTRUCTIONS: |
| PMEV_SET_MASK(sel->ev.event, 0x00); |
| PMEV_SET_EVENT(sel->ev.event, 0xc4); |
| break; |
| case PERF_COUNT_HW_BRANCH_MISSES: |
| PMEV_SET_MASK(sel->ev.event, 0x00); |
| PMEV_SET_EVENT(sel->ev.event, 0xc5); |
| break; |
| case PERF_COUNT_HW_BUS_CYCLES: |
| /* Unhalted reference cycles */ |
| PMEV_SET_MASK(sel->ev.event, 0x01); |
| PMEV_SET_EVENT(sel->ev.event, 0x3c); |
| break; |
| default: |
| return FALSE; |
| }; |
| break; |
| default: |
| return FALSE; |
| }; |
| /* This will make sure we use fixed counters if available */ |
| x86_handle_pseudo_encoding(sel); |
| return TRUE; |
| } |
| |
| /* Parse the string for a built-in encoding. These are the perf defaults such |
| * as 'cycles' or 'cache-references.' Returns TRUE on success and fills in parts |
| * of sel. */ |
| static bool parse_generic_encoding(const char *str, struct perf_eventsel *sel) |
| { |
| bool ret = FALSE; |
| char *colon; |
| |
| if (!generic_str_get_code(str, sel)) |
| return FALSE; |
| switch (sel->type) { |
| case PERF_TYPE_HARDWARE: |
| case PERF_TYPE_HW_CACHE: |
| ret = arch_translate_generic(sel); |
| break; |
| }; |
| if (!ret) { |
| fprintf(stderr, "Unsupported built-in event %s\n", str); |
| return FALSE; |
| } |
| strlcpy(sel->fq_str, str, MAX_FQSTR_SZ); |
| colon = strchr(str, ':'); |
| if (colon) |
| parse_modifiers(colon + 1, sel); |
| 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. */ |
| struct perf_eventsel *perf_parse_event(const char *str) |
| { |
| struct perf_eventsel *sel = xzmalloc(sizeof(struct perf_eventsel)); |
| |
| sel->ev.user_data = (uint64_t)sel; |
| if (parse_generic_encoding(str, sel)) |
| goto success; |
| if (parse_pfm_encoding(str, sel)) |
| goto success; |
| if (parse_raw_encoding(str, sel)) |
| goto success; |
| free(sel); |
| 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); |
| } |
| PMEV_SET_EN(sel->ev.event, 1); |
| return sel; |
| } |
| |
| static void perf_get_arch_info(int perf_fd, struct perf_arch_info *pai) |
| { |
| uint8_t cmdbuf[6 * sizeof(uint32_t)]; |
| const uint8_t *rptr = cmdbuf; |
| |
| cmdbuf[0] = PERFMON_CMD_CPU_CAPS; |
| |
| xpwrite(perf_fd, cmdbuf, 1, 0); |
| xpread(perf_fd, cmdbuf, 6 * sizeof(uint32_t), 0); |
| |
| rptr = get_le_u32(rptr, &pai->perfmon_version); |
| rptr = get_le_u32(rptr, &pai->proc_arch_events); |
| rptr = get_le_u32(rptr, &pai->bits_x_counter); |
| rptr = get_le_u32(rptr, &pai->counters_x_proc); |
| rptr = get_le_u32(rptr, &pai->bits_x_fix_counter); |
| rptr = get_le_u32(rptr, &pai->fix_counters_x_proc); |
| } |
| |
| static int perf_open_event(int perf_fd, const struct core_set *cores, |
| const struct perf_eventsel *sel) |
| { |
| uint8_t cmdbuf[1 + 3 * sizeof(uint64_t) + sizeof(uint32_t) + |
| CORE_SET_SIZE]; |
| uint8_t *wptr = cmdbuf; |
| const uint8_t *rptr = cmdbuf; |
| uint32_t ped; |
| int i, j; |
| |
| *wptr++ = PERFMON_CMD_COUNTER_OPEN; |
| wptr = put_le_u64(wptr, sel->ev.event); |
| wptr = put_le_u64(wptr, sel->ev.flags); |
| wptr = put_le_u64(wptr, sel->ev.trigger_count); |
| wptr = put_le_u64(wptr, sel->ev.user_data); |
| |
| for (i = CORE_SET_SIZE - 1; (i >= 0) && !cores->core_set[i]; i--) |
| ; |
| if (i < 0) { |
| fprintf(stderr, |
| "Performance event CPU set must not be empty\n"); |
| exit(1); |
| } |
| wptr = put_le_u32(wptr, i + 1); |
| for (j = 0; j <= i; j++) |
| *wptr++ = cores->core_set[j]; |
| |
| xpwrite(perf_fd, cmdbuf, wptr - cmdbuf, 0); |
| xpread(perf_fd, cmdbuf, sizeof(uint32_t), 0); |
| |
| rptr = get_le_u32(rptr, &ped); |
| |
| return (int) ped; |
| } |
| |
| static uint64_t *perf_get_event_values(int perf_fd, int ped, size_t *pnvalues) |
| { |
| ssize_t rsize; |
| uint32_t i, n; |
| uint64_t *values; |
| uint64_t temp; |
| size_t bufsize = sizeof(uint32_t) + MAX_NUM_CORES * sizeof(uint64_t); |
| uint8_t *cmdbuf = xmalloc(bufsize); |
| uint8_t *wptr = cmdbuf; |
| const uint8_t *rptr = cmdbuf; |
| |
| *wptr++ = PERFMON_CMD_COUNTER_STATUS; |
| wptr = put_le_u32(wptr, ped); |
| |
| xpwrite(perf_fd, cmdbuf, wptr - cmdbuf, 0); |
| rsize = pread(perf_fd, cmdbuf, bufsize, 0); |
| |
| if (rsize < (sizeof(uint32_t))) { |
| fprintf(stderr, |
| "Invalid read size while fetching event status: %ld\n", |
| rsize); |
| exit(1); |
| } |
| rptr = get_le_u32(rptr, &n); |
| if (((rptr - cmdbuf) + n * sizeof(uint64_t)) > rsize) { |
| fprintf(stderr, |
| "Invalid read size while fetching event status: %ld\n", |
| rsize); |
| exit(1); |
| } |
| values = xmalloc(n * sizeof(uint64_t)); |
| for (i = 0; i < n; i++) |
| rptr = get_le_u64(rptr, values + i); |
| free(cmdbuf); |
| |
| *pnvalues = n; |
| |
| return values; |
| } |
| |
| /* Helper, returns the total count (across all cores) of the event @idx */ |
| uint64_t perf_get_event_count(struct perf_context *pctx, unsigned int idx) |
| { |
| uint64_t total = 0; |
| size_t nvalues; |
| uint64_t *values; |
| |
| values = perf_get_event_values(pctx->perf_fd, pctx->events[idx].ped, |
| &nvalues); |
| for (int i = 0; i < nvalues; i++) |
| total += values[i]; |
| free(values); |
| return total; |
| } |
| |
| static void perf_close_event(int perf_fd, int ped) |
| { |
| uint8_t cmdbuf[1 + sizeof(uint32_t)]; |
| uint8_t *wptr = cmdbuf; |
| |
| *wptr++ = PERFMON_CMD_COUNTER_CLOSE; |
| wptr = put_le_u32(wptr, ped); |
| |
| xpwrite(perf_fd, cmdbuf, wptr - cmdbuf, 0); |
| } |
| |
| struct perf_context *perf_create_context(struct perf_context_config *cfg) |
| { |
| struct perf_context *pctx = xzmalloc(sizeof(struct perf_context)); |
| |
| pctx->cfg = cfg; |
| pctx->perf_fd = xopen(cfg->perf_file, O_RDWR, 0); |
| /* perf record needs kpctl_fd, but other perf subcommands might not. |
| * We'll delay the opening of kpctl until we need it, since kprof is |
| * picky about multiple users of kpctl. */ |
| pctx->kpctl_fd = -1; |
| perf_get_arch_info(pctx->perf_fd, &pctx->pai); |
| |
| return pctx; |
| } |
| |
| void perf_free_context(struct perf_context *pctx) |
| { |
| if (pctx->kpctl_fd != -1) |
| close(pctx->kpctl_fd); /* disabled sampling */ |
| close(pctx->perf_fd); /* closes all events */ |
| free(pctx); |
| } |
| |
| void perf_context_event_submit(struct perf_context *pctx, |
| const struct core_set *cores, |
| const struct perf_eventsel *sel) |
| { |
| struct perf_event *pevt = pctx->events + pctx->event_count; |
| |
| if (pctx->event_count >= COUNT_OF(pctx->events)) { |
| fprintf(stderr, "Too many open events: %d\n", |
| pctx->event_count); exit(1); |
| } |
| pctx->event_count++; |
| pevt->cores = *cores; |
| pevt->sel = *sel; |
| pevt->ped = perf_open_event(pctx->perf_fd, cores, sel); |
| if (pevt->ped < 0) { |
| fprintf(stderr, "Unable to submit event \"%s\": %s\n", |
| sel->fq_str, errstr()); |
| exit(1); |
| } |
| } |
| |
| void perf_stop_events(struct perf_context *pctx) |
| { |
| for (int i = 0; i < pctx->event_count; i++) |
| perf_close_event(pctx->perf_fd, pctx->events[i].ped); |
| } |
| |
| static void ensure_kpctl_is_open(struct perf_context *pctx) |
| { |
| if (pctx->kpctl_fd == -1) |
| pctx->kpctl_fd = xopen(pctx->cfg->kpctl_file, O_RDWR, 0); |
| } |
| |
| void perf_start_sampling(struct perf_context *pctx) |
| { |
| static const char * const enable_str = "start"; |
| |
| ensure_kpctl_is_open(pctx); |
| xwrite(pctx->kpctl_fd, enable_str, strlen(enable_str)); |
| } |
| |
| void perf_stop_sampling(struct perf_context *pctx) |
| { |
| static const char * const disable_str = "stop"; |
| |
| ensure_kpctl_is_open(pctx); |
| xwrite(pctx->kpctl_fd, disable_str, strlen(disable_str)); |
| } |
| |
| void perf_context_show_events(struct perf_context *pctx, FILE *file) |
| { |
| struct perf_eventsel *sel; |
| |
| for (int i = 0; i < pctx->event_count; i++) { |
| sel = &pctx->events[i].sel; |
| fprintf(file, "Event: %s, final code %p%s, trigger count %d\n", |
| sel->fq_str, sel->ev.event, |
| perfmon_is_fixed_event(&sel->ev) ? " (fixed)" : "", |
| sel->ev.trigger_count); |
| } |
| } |
| |
| static void perf_print_event_flags(const pfm_event_info_t *info, FILE *file) |
| { |
| int n = 0; |
| |
| if (info->is_precise) { |
| fputs("[precise] ", file); |
| n++; |
| } |
| if (!n) |
| fputs("None", file); |
| } |
| |
| static void perf_print_attr_flags(const pfm_event_attr_info_t *info, FILE *file) |
| { |
| int n = 0; |
| |
| if (info->is_dfl) { |
| fputs("[default] ", file); |
| n++; |
| } |
| if (info->is_precise) { |
| fputs("[precise] ", file); |
| n++; |
| } |
| if (!n) |
| 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) |
| { |
| static const char * const srcs[PFM_ATTR_CTRL_MAX] = { |
| [PFM_ATTR_CTRL_UNKNOWN] = "???", |
| [PFM_ATTR_CTRL_PMU] = "PMU", |
| [PFM_ATTR_CTRL_PERF_EVENT] = "perf_event", |
| }; |
| pfm_event_attr_info_t ainfo; |
| int i, mod = 0, um = 0; |
| |
| fprintf(file, "#-----------------------------\n" |
| "IDX : %d\n" |
| "PMU name : %s (%s)\n" |
| "Name : %s\n" |
| "Equiv : %s\n", |
| info->idx, pinfo->name, pinfo->desc, |
| info->name, info->equiv ? info->equiv : "None"); |
| |
| fprintf(file, "Flags : "); |
| perf_print_event_flags(info, file); |
| fputc('\n', file); |
| |
| fprintf(file, "Desc : %s\n", info->desc ? info->desc : |
| "no description available"); |
| fprintf(file, "Code : 0x%"PRIx64"\n", info->code); |
| |
| ZERO_DATA(ainfo); |
| ainfo.size = sizeof(ainfo); |
| |
| pfm_for_each_event_attr(i, info) { |
| const char *src; |
| pfm_err_t err = pfm_get_event_attr_info(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.ctrl >= PFM_ATTR_CTRL_MAX) { |
| fprintf(stderr, |
| "event: %s has unsupported attribute source %d", |
| info->name, ainfo.ctrl); |
| ainfo.ctrl = PFM_ATTR_CTRL_UNKNOWN; |
| } |
| src = srcs[ainfo.ctrl]; |
| switch (ainfo.type) { |
| case PFM_ATTR_UMASK: |
| fprintf(file, |
| "Umask-%02u : 0x%02"PRIx64" : %s : [%s] : ", |
| um, ainfo.code, src, ainfo.name); |
| perf_print_attr_flags(&ainfo, file); |
| fputc(':', file); |
| if (ainfo.equiv) |
| fprintf(file, " Alias to %s", |
| ainfo.equiv); |
| else |
| fprintf(file, " %s", ainfo.desc); |
| fputc('\n', file); |
| um++; |
| break; |
| case PFM_ATTR_MOD_BOOL: |
| fprintf(file, |
| "Modif-%02u : 0x%02"PRIx64" : %s : [%s] : " |
| "%s (boolean)\n", mod, ainfo.code, src, |
| ainfo.name, ainfo.desc); |
| mod++; |
| break; |
| case PFM_ATTR_MOD_INTEGER: |
| fprintf(file, |
| "Modif-%02u : 0x%02"PRIx64" : %s : [%s] : " |
| "%s (integer)\n", mod, ainfo.code, src, |
| ainfo.name, ainfo.desc); |
| mod++; |
| break; |
| default: |
| fprintf(file, |
| "Attr-%02u : 0x%02"PRIx64" : %s : [%s] : %s\n", |
| i, ainfo.code, ainfo.name, src, ainfo.desc); |
| } |
| } |
| } |
| |
| void perf_show_events(const char *rx, FILE *file) |
| { |
| int pmu; |
| regex_t crx; |
| pfm_pmu_info_t pinfo; |
| pfm_event_info_t info; |
| char fullname[256]; |
| |
| if (rx && regcomp(&crx, rx, REG_ICASE)) { |
| fprintf(stderr, "Failed to compile event regex: '%s'\n", rx); |
| exit(1); |
| } |
| |
| 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)) { |
| 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); |
| } |
| snprintf(fullname, sizeof(fullname), "%s::%s", |
| pinfo.name, info.name); |
| if (!rx || regexec(&crx, fullname, 0, NULL, 0) == 0) |
| perf_show_event_info(&info, &pinfo, file); |
| } |
| } |
| if (rx) |
| regfree(&crx); |
| } |
| |
| void perf_convert_trace_data(struct perfconv_context *cctx, const char *input, |
| FILE *outfile) |
| { |
| FILE *infile; |
| size_t ksize; |
| |
| infile = xfopen(input, "rb"); |
| if (xfsize(infile) > 0) { |
| perfconv_add_kernel_mmap(cctx); |
| perfconv_add_kernel_buildid(cctx); |
| perfconv_process_input(cctx, infile, outfile); |
| } |
| fclose(infile); |
| } |