diff options
author | Ingo Molnar <mingo@kernel.org> | 2016-10-04 10:04:47 +0200 |
---|---|---|
committer | Ingo Molnar <mingo@kernel.org> | 2016-10-04 10:04:47 +0200 |
commit | 8657355f5b5f657407efc12a2223e8a3a6d658de (patch) | |
tree | 29608a0c914ce6f8fc67a90bb1df282eac58a20a | |
parent | 597f03f9d133e9837d00965016170271d4f87dcf (diff) | |
parent | b42c7369e3f451e22c2b0be5d193955498d37546 (diff) |
Merge tag 'perf-core-for-mingo-20161003' of git://git.kernel.org/pub/scm/linux/kernel/git/acme/linux into perf/urgent
Pull perf/core improvements and fixes:
- Allow vendors to provide JSON files describing PMU events, that then
get parsed to generate C tables that are linked against perf, allowing
the use of the names in their documentations, such as:
# perf list l1d
List of pre-defined events (to be used in -e):
Cache:
l1d.replacement
[L1D data line replacements]
l1d_pend_miss.fb_full
[Cycles a demand request was blocked due to Fill Buffers inavailability]
l1d_pend_miss.pending
[L1D miss oustandings duration in cycles]
l1d_pend_miss.pending_cycles
[Cycles with L1D load Misses outstanding]
l1d_pend_miss.pending_cycles_any
[Cycles with L1D load Misses outstanding from any thread on physical core]
l2_trans.l1d_wb
[L1D writebacks that access L2 cache]
Pipeline:
cycle_activity.cycles_l1d_miss
[Cycles while L1 cache miss demand load is outstanding]
cycle_activity.cycles_l1d_pending
[Cycles while L1 cache miss demand load is outstanding]
cycle_activity.stalls_l1d_miss
[Execution stalls while L1 cache miss demand load is outstanding]
cycle_activity.stalls_l1d_pending
[Execution stalls while L1 cache miss demand load is outstanding]
The above example was done on a Broadwell based ThinkPad t450s after
downloading and installing such JSON files which will be added to the
tools/perf/pmu-events/ directory in a subsequent patchkit.
Now one can use those names with -e/--event in all 'perf tools'.
(Andi Kleen, Sukadev Bhattiprolu)
- Add a missing pointer dereference in 'perf probe' (Colin Ian King)
- Add support for building host programs to be used in generating files
to be used in the build process, such as fixdep and jevents, fixing
the usage of these features in a cross compilation setup (Jiri Olsa)
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Signed-off-by: Ingo Molnar <mingo@kernel.org>
32 files changed, 1926 insertions, 66 deletions
diff --git a/tools/build/Build b/tools/build/Build index 63a6c34c0c88..76d1a4960973 100644 --- a/tools/build/Build +++ b/tools/build/Build @@ -1 +1,3 @@ +hostprogs := fixdep + fixdep-y := fixdep.o diff --git a/tools/build/Build.include b/tools/build/Build.include index 4d000bc959b4..02489380d79b 100644 --- a/tools/build/Build.include +++ b/tools/build/Build.include @@ -90,3 +90,8 @@ if_changed = $(if $(strip $(any-prereq) $(arg-check)), \ # - per object C flags # - BUILD_STR macro to allow '-D"$(variable)"' constructs c_flags = -Wp,-MD,$(depfile),-MT,$@ $(CFLAGS) -D"BUILD_STR(s)=\#s" $(CFLAGS_$(basetarget).o) $(CFLAGS_$(obj)) + +### +## HOSTCC C flags + +host_c_flags = -Wp,-MD,$(depfile),-MT,$@ $(CHOSTFLAGS) -D"BUILD_STR(s)=\#s" $(CHOSTFLAGS_$(basetarget).o) $(CHOSTFLAGS_$(obj)) diff --git a/tools/build/Makefile b/tools/build/Makefile index 0d5a0e3a8fa9..8332959fbca4 100644 --- a/tools/build/Makefile +++ b/tools/build/Makefile @@ -14,6 +14,12 @@ endef $(call allow-override,CC,$(CROSS_COMPILE)gcc) $(call allow-override,LD,$(CROSS_COMPILE)ld) +HOSTCC ?= gcc +HOSTLD ?= ld +HOSTAR ?= ar + +export HOSTCC HOSTLD HOSTAR + ifeq ($(V),1) Q = else @@ -36,7 +42,7 @@ $(OUTPUT)fixdep-in.o: FORCE $(Q)$(MAKE) $(build)=fixdep $(OUTPUT)fixdep: $(OUTPUT)fixdep-in.o - $(QUIET_LINK)$(CC) $(LDFLAGS) -o $@ $< + $(QUIET_LINK)$(HOSTCC) $(LDFLAGS) -o $@ $< FORCE: diff --git a/tools/build/Makefile.build b/tools/build/Makefile.build index 27f3583193e6..190519a94ce5 100644 --- a/tools/build/Makefile.build +++ b/tools/build/Makefile.build @@ -58,6 +58,9 @@ quiet_cmd_mkdir = MKDIR $(dir $@) quiet_cmd_cc_o_c = CC $@ cmd_cc_o_c = $(CC) $(c_flags) -c -o $@ $< +quiet_cmd_host_cc_o_c = HOSTCC $@ + cmd_host_cc_o_c = $(HOSTCC) $(host_c_flags) -c -o $@ $< + quiet_cmd_cpp_i_c = CPP $@ cmd_cpp_i_c = $(CC) $(c_flags) -E -o $@ $< @@ -70,16 +73,24 @@ quiet_cmd_gen = GEN $@ # If there's nothing to link, create empty $@ object. quiet_cmd_ld_multi = LD $@ cmd_ld_multi = $(if $(strip $(obj-y)),\ - $(LD) -r -o $@ $(filter $(obj-y),$^),rm -f $@; $(AR) rcs $@) + $(LD) -r -o $@ $(filter $(obj-y),$^),rm -f $@; $(AR) rcs $@) + +quiet_cmd_host_ld_multi = HOSTLD $@ + cmd_host_ld_multi = $(if $(strip $(obj-y)),\ + $(HOSTLD) -r -o $@ $(filter $(obj-y),$^),rm -f $@; $(HOSTAR) rcs $@) + +ifneq ($(filter $(obj),$(hostprogs)),) + host = host_ +endif # Build rules $(OUTPUT)%.o: %.c FORCE $(call rule_mkdir) - $(call if_changed_dep,cc_o_c) + $(call if_changed_dep,$(host)cc_o_c) $(OUTPUT)%.o: %.S FORCE $(call rule_mkdir) - $(call if_changed_dep,cc_o_c) + $(call if_changed_dep,$(host)cc_o_c) $(OUTPUT)%.i: %.c FORCE $(call rule_mkdir) @@ -119,7 +130,7 @@ $(sort $(subdir-obj-y)): $(subdir-y) ; $(in-target): $(obj-y) FORCE $(call rule_mkdir) - $(call if_changed,ld_multi) + $(call if_changed,$(host)ld_multi) __build: $(in-target) @: diff --git a/tools/build/Makefile.include b/tools/build/Makefile.include index be630bed66d2..ad22e4e7bc59 100644 --- a/tools/build/Makefile.include +++ b/tools/build/Makefile.include @@ -1,10 +1,6 @@ build := -f $(srctree)/tools/build/Makefile.build dir=. obj -ifdef CROSS_COMPILE -fixdep: -else fixdep: $(Q)$(MAKE) -C $(srctree)/tools/build CFLAGS= LDFLAGS= $(OUTPUT)fixdep -endif .PHONY: fixdep diff --git a/tools/lib/subcmd/pager.c b/tools/lib/subcmd/pager.c index d50f3b58606b..6518bea926d6 100644 --- a/tools/lib/subcmd/pager.c +++ b/tools/lib/subcmd/pager.c @@ -3,6 +3,7 @@ #include <stdio.h> #include <string.h> #include <signal.h> +#include <sys/ioctl.h> #include "pager.h" #include "run-command.h" #include "sigchain.h" @@ -14,6 +15,7 @@ */ static int spawned_pager; +static int pager_columns; void pager_init(const char *pager_env) { @@ -58,9 +60,12 @@ static void wait_for_pager_signal(int signo) void setup_pager(void) { const char *pager = getenv(subcmd_config.pager_env); + struct winsize sz; if (!isatty(1)) return; + if (ioctl(1, TIOCGWINSZ, &sz) == 0) + pager_columns = sz.ws_col; if (!pager) pager = getenv("PAGER"); if (!(pager || access("/usr/bin/pager", X_OK))) @@ -98,3 +103,14 @@ int pager_in_use(void) { return spawned_pager; } + +int pager_get_columns(void) +{ + char *s; + + s = getenv("COLUMNS"); + if (s) + return atoi(s); + + return (pager_columns ? pager_columns : 80) - 2; +} diff --git a/tools/lib/subcmd/pager.h b/tools/lib/subcmd/pager.h index 8b83714ecf73..623f5542d05d 100644 --- a/tools/lib/subcmd/pager.h +++ b/tools/lib/subcmd/pager.h @@ -5,5 +5,6 @@ extern void pager_init(const char *pager_env); extern void setup_pager(void); extern int pager_in_use(void); +extern int pager_get_columns(void); #endif /* __SUBCMD_PAGER_H */ diff --git a/tools/perf/Documentation/perf-list.txt b/tools/perf/Documentation/perf-list.txt index a126e97a8114..41857cce5e86 100644 --- a/tools/perf/Documentation/perf-list.txt +++ b/tools/perf/Documentation/perf-list.txt @@ -8,13 +8,23 @@ perf-list - List all symbolic event types SYNOPSIS -------- [verse] -'perf list' [hw|sw|cache|tracepoint|pmu|event_glob] +'perf list' [--no-desc] [--long-desc] [hw|sw|cache|tracepoint|pmu|event_glob] DESCRIPTION ----------- This command displays the symbolic event types which can be selected in the various perf commands with the -e option. +OPTIONS +------- +--no-desc:: +Don't print descriptions. + +-v:: +--long-desc:: +Print longer event descriptions. + + [[EVENT_MODIFIERS]] EVENT MODIFIERS --------------- diff --git a/tools/perf/Makefile.perf b/tools/perf/Makefile.perf index d710db16b963..982d6439bb07 100644 --- a/tools/perf/Makefile.perf +++ b/tools/perf/Makefile.perf @@ -144,6 +144,10 @@ $(call allow-override,LD,$(CROSS_COMPILE)ld) LD += $(EXTRA_LDFLAGS) +HOSTCC ?= gcc +HOSTLD ?= ld +HOSTAR ?= ar + PKG_CONFIG = $(CROSS_COMPILE)pkg-config RM = rm -f @@ -345,8 +349,18 @@ strip: $(PROGRAMS) $(OUTPUT)perf PERF_IN := $(OUTPUT)perf-in.o export srctree OUTPUT RM CC LD AR CFLAGS V BISON FLEX AWK +export HOSTCC HOSTLD HOSTAR include $(srctree)/tools/build/Makefile.include +JEVENTS := $(OUTPUT)pmu-events/jevents +JEVENTS_IN := $(OUTPUT)pmu-events/jevents-in.o + +PMU_EVENTS_IN := $(OUTPUT)pmu-events/pmu-events-in.o + +export JEVENTS + +build := -f $(srctree)/tools/build/Makefile.build dir=. obj + $(PERF_IN): prepare FORCE @(test -f ../../include/uapi/linux/perf_event.h && ( \ (diff -B ../include/uapi/linux/perf_event.h ../../include/uapi/linux/perf_event.h >/dev/null) \ @@ -443,9 +457,18 @@ $(PERF_IN): prepare FORCE || echo "Warning: tools/include/uapi/linux/mman.h differs from kernel" >&2 )) || true $(Q)$(MAKE) $(build)=perf -$(OUTPUT)perf: $(PERFLIBS) $(PERF_IN) $(LIBTRACEEVENT_DYNAMIC_LIST) +$(JEVENTS_IN): FORCE + $(Q)$(MAKE) -f $(srctree)/tools/build/Makefile.build dir=pmu-events obj=jevents + +$(JEVENTS): $(JEVENTS_IN) + $(QUIET_LINK)$(HOSTCC) $(JEVENTS_IN) -o $@ + +$(PMU_EVENTS_IN): $(JEVENTS) FORCE + $(Q)$(MAKE) -f $(srctree)/tools/build/Makefile.build dir=pmu-events obj=pmu-events + +$(OUTPUT)perf: $(PERFLIBS) $(PERF_IN) $(PMU_EVENTS_IN) $(LIBTRACEEVENT_DYNAMIC_LIST) $(QUIET_LINK)$(CC) $(CFLAGS) $(LDFLAGS) $(LIBTRACEEVENT_DYNAMIC_LIST_LDFLAGS) \ - $(PERF_IN) $(LIBS) -o $@ + $(PERF_IN) $(PMU_EVENTS_IN) $(LIBS) -o $@ $(GTK_IN): fixdep FORCE $(Q)$(MAKE) $(build)=gtk @@ -474,6 +497,8 @@ perf.spec $(SCRIPTS) \ ifneq ($(OUTPUT),) %.o: $(OUTPUT)%.o @echo " # Redirected target $@ => $(OUTPUT)$@" +pmu-events/%.o: $(OUTPUT)pmu-events/%.o + @echo " # Redirected target $@ => $(OUTPUT)$@" util/%.o: $(OUTPUT)util/%.o @echo " # Redirected target $@ => $(OUTPUT)$@" bench/%.o: $(OUTPUT)bench/%.o @@ -729,10 +754,11 @@ clean:: $(LIBTRACEEVENT)-clean $(LIBAPI)-clean $(LIBBPF)-clean $(LIBSUBCMD)-clea $(call QUIET_CLEAN, core-objs) $(RM) $(LIB_FILE) $(OUTPUT)perf-archive $(OUTPUT)perf-with-kcore $(LANG_BINDINGS) $(Q)find $(if $(OUTPUT),$(OUTPUT),.) -name '*.o' -delete -o -name '\.*.cmd' -delete -o -name '\.*.d' -delete $(Q)$(RM) $(OUTPUT).config-detected - $(call QUIET_CLEAN, core-progs) $(RM) $(ALL_PROGRAMS) perf perf-read-vdso32 perf-read-vdsox32 + $(call QUIET_CLEAN, core-progs) $(RM) $(ALL_PROGRAMS) perf perf-read-vdso32 perf-read-vdsox32 $(OUTPUT)pmu-events/jevents $(call QUIET_CLEAN, core-gen) $(RM) *.spec *.pyc *.pyo */*.pyc */*.pyo $(OUTPUT)common-cmds.h TAGS tags cscope* $(OUTPUT)PERF-VERSION-FILE $(OUTPUT)FEATURE-DUMP $(OUTPUT)util/*-bison* $(OUTPUT)util/*-flex* \ $(OUTPUT)util/intel-pt-decoder/inat-tables.c $(OUTPUT)fixdep \ - $(OUTPUT)tests/llvm-src-{base,kbuild,prologue,relocation}.c + $(OUTPUT)tests/llvm-src-{base,kbuild,prologue,relocation}.c \ + $(OUTPUT)pmu-events/pmu-events.c $(QUIET_SUBDIR0)Documentation $(QUIET_SUBDIR1) clean $(python-clean) diff --git a/tools/perf/arch/powerpc/util/header.c b/tools/perf/arch/powerpc/util/header.c index f8ccee132867..9aaa6f5a9347 100644 --- a/tools/perf/arch/powerpc/util/header.c +++ b/tools/perf/arch/powerpc/util/header.c @@ -32,3 +32,14 @@ get_cpuid(char *buffer, size_t sz) } return -1; } + +char * +get_cpuid_str(void) +{ + char *bufp; + + if (asprintf(&bufp, "%.8lx", mfspr(SPRN_PVR)) < 0) + bufp = NULL; + + return bufp; +} diff --git a/tools/perf/arch/x86/util/header.c b/tools/perf/arch/x86/util/header.c index 146d12a1cec0..a74a48db26f5 100644 --- a/tools/perf/arch/x86/util/header.c +++ b/tools/perf/arch/x86/util/header.c @@ -19,8 +19,8 @@ cpuid(unsigned int op, unsigned int *a, unsigned int *b, unsigned int *c, : "a" (op)); } -int -get_cpuid(char *buffer, size_t sz) +static int +__get_cpuid(char *buffer, size_t sz, const char *fmt) { unsigned int a, b, c, d, lvl; int family = -1, model = -1, step = -1; @@ -48,7 +48,7 @@ get_cpuid(char *buffer, size_t sz) if (family >= 0x6) model += ((a >> 16) & 0xf) << 4; } - nb = scnprintf(buffer, sz, "%s,%u,%u,%u$", vendor, family, model, step); + nb = scnprintf(buffer, sz, fmt, vendor, family, model, step); /* look for end marker to ensure the entire data fit */ if (strchr(buffer, '$')) { @@ -57,3 +57,21 @@ get_cpuid(char *buffer, size_t sz) } return -1; } + +int +get_cpuid(char *buffer, size_t sz) +{ + return __get_cpuid(buffer, sz, "%s,%u,%u,%u$"); +} + +char * +get_cpuid_str(void) +{ + char *buf = malloc(128); + + if (__get_cpuid(buf, 128, "%s-%u-%X$") < 0) { + free(buf); + return NULL; + } + return buf; +} diff --git a/tools/perf/builtin-list.c b/tools/perf/builtin-list.c index 88ee419e5189..ba9322ff858b 100644 --- a/tools/perf/builtin-list.c +++ b/tools/perf/builtin-list.c @@ -16,16 +16,23 @@ #include "util/pmu.h" #include <subcmd/parse-options.h> +static bool desc_flag = true; + int cmd_list(int argc, const char **argv, const char *prefix __maybe_unused) { int i; bool raw_dump = false; + bool long_desc_flag = false; struct option list_options[] = { OPT_BOOLEAN(0, "raw-dump", &raw_dump, "Dump raw events"), + OPT_BOOLEAN('d', "desc", &desc_flag, + "Print extra event descriptions. --no-desc to not print."), + OPT_BOOLEAN('v', "long-desc", &long_desc_flag, + "Print longer event descriptions."), OPT_END() }; const char * const list_usage[] = { - "perf list [hw|sw|cache|tracepoint|pmu|sdt|event_glob]", + "perf list [<options>] [hw|sw|cache|tracepoint|pmu|sdt|event_glob]", NULL }; @@ -40,7 +47,7 @@ int cmd_list(int argc, const char **argv, const char *prefix __maybe_unused) printf("\nList of pre-defined events (to be used in -e):\n\n"); if (argc == 0) { - print_events(NULL, raw_dump); + print_events(NULL, raw_dump, !desc_flag, long_desc_flag); return 0; } @@ -61,14 +68,16 @@ int cmd_list(int argc, const char **argv, const char *prefix __maybe_unused) strcmp(argv[i], "hwcache") == 0) print_hwcache_events(NULL, raw_dump); else if (strcmp(argv[i], "pmu") == 0) - print_pmu_events(NULL, raw_dump); + print_pmu_events(NULL, raw_dump, !desc_flag, + long_desc_flag); else if (strcmp(argv[i], "sdt") == 0) print_sdt_events(NULL, NULL, raw_dump); else if ((sep = strchr(argv[i], ':')) != NULL) { int sep_idx; if (sep == NULL) { - print_events(argv[i], raw_dump); + print_events(argv[i], raw_dump, !desc_flag, + long_desc_flag); continue; } sep_idx = sep - argv[i]; @@ -90,7 +99,8 @@ int cmd_list(int argc, const char **argv, const char *prefix __maybe_unused) print_symbol_events(s, PERF_TYPE_SOFTWARE, event_symbols_sw, PERF_COUNT_SW_MAX, raw_dump); print_hwcache_events(s, raw_dump); - print_pmu_events(s, raw_dump); + print_pmu_events(s, raw_dump, !desc_flag, + long_desc_flag); print_tracepoint_events(NULL, s, raw_dump); print_sdt_events(NULL, s, raw_dump); free(s); diff --git a/tools/perf/pmu-events/Build b/tools/perf/pmu-events/Build new file mode 100644 index 000000000000..9213a1273697 --- /dev/null +++ b/tools/perf/pmu-events/Build @@ -0,0 +1,13 @@ +hostprogs := jevents + +jevents-y += json.o jsmn.o jevents.o +pmu-events-y += pmu-events.o +JDIR = pmu-events/arch/$(ARCH) +JSON = $(shell [ -d $(JDIR) ] && \ + find $(JDIR) -name '*.json' -o -name 'mapfile.csv') +# +# Locate/process JSON files in pmu-events/arch/ +# directory and create tables in pmu-events.c. +# +$(OUTPUT)pmu-events/pmu-events.c: $(JSON) $(JEVENTS) + $(Q)$(call echo-cmd,gen)$(JEVENTS) $(ARCH) pmu-events/arch $(OUTPUT)pmu-events/pmu-events.c $(V) diff --git a/tools/perf/pmu-events/README b/tools/perf/pmu-events/README new file mode 100644 index 000000000000..1408ade0d773 --- /dev/null +++ b/tools/perf/pmu-events/README @@ -0,0 +1,147 @@ + +The contents of this directory allow users to specify PMU events in their +CPUs by their symbolic names rather than raw event codes (see example below). + +The main program in this directory, is the 'jevents', which is built and +executed _BEFORE_ the perf binary itself is built. + +The 'jevents' program tries to locate and process JSON files in the directory +tree tools/perf/pmu-events/arch/foo. + + - Regular files with '.json' extension in the name are assumed to be + JSON files, each of which describes a set of PMU events. + + - Regular files with basename starting with 'mapfile.csv' are assumed + to be a CSV file that maps a specific CPU to its set of PMU events. + (see below for mapfile format) + + - Directories are traversed, but all other files are ignored. + +The PMU events supported by a CPU model are expected to grouped into topics +such as Pipelining, Cache, Memory, Floating-point etc. All events for a topic +should be placed in a separate JSON file - where the file name identifies +the topic. Eg: "Floating-point.json". + +All the topic JSON files for a CPU model/family should be in a separate +sub directory. Thus for the Silvermont X86 CPU: + + $ ls tools/perf/pmu-events/arch/x86/Silvermont_core + Cache.json Memory.json Virtual-Memory.json + Frontend.json Pipeline.json + +Using the JSON files and the mapfile, 'jevents' generates the C source file, +'pmu-events.c', which encodes the two sets of tables: + + - Set of 'PMU events tables' for all known CPUs in the architecture, + (one table like the following, per JSON file; table name 'pme_power8' + is derived from JSON file name, 'power8.json'). + + struct pmu_event pme_power8[] = { + + ... + + { + .name = "pm_1plus_ppc_cmpl", + .event = "event=0x100f2", + .desc = "1 or more ppc insts finished,", + }, + + ... + } + + - A 'mapping table' that maps each CPU of the architecture, to its + 'PMU events table' + + struct pmu_events_map pmu_events_map[] = { + { + .cpuid = "004b0000", + .version = "1", + .type = "core", + .table = pme_power8 + }, + ... + + }; + +After the 'pmu-events.c' is generated, it is compiled and the resulting +'pmu-events.o' is added to 'libperf.a' which is then used to build perf. + +NOTES: + 1. Several CPUs can support same set of events and hence use a common + JSON file. Hence several entries in the pmu_events_map[] could map + to a single 'PMU events table'. + + 2. The 'pmu-events.h' has an extern declaration for the mapping table + and the generated 'pmu-events.c' defines this table. + + 3. _All_ known CPU tables for architecture are included in the perf + binary. + +At run time, perf determines the actual CPU it is running on, finds the +matching events table and builds aliases for those events. This allows +users to specify events by their name: + + $ perf stat -e pm_1plus_ppc_cmpl sleep 1 + +where 'pm_1plus_ppc_cmpl' is a Power8 PMU event. + +In case of errors when processing files in the tools/perf/pmu-events/arch +directory, 'jevents' tries to create an empty mapping file to allow the perf +build to succeed even if the PMU event aliases cannot be used. + +However some errors in processing may cause the perf build to fail. + +Mapfile format +=============== + +The mapfile enables multiple CPU models to share a single set of PMU events. +It is required even if such mapping is 1:1. + +The mapfile.csv format is expected to be: + + Header line + CPUID,Version,Dir/path/name,Type + +where: + + Comma: + is the required field delimiter (i.e other fields cannot + have commas within them). + + Comments: + Lines in which the first character is either '\n' or '#' + are ignored. + + Header line + The header line is the first line in the file, which is + always _IGNORED_. It can empty. + + CPUID: + CPUID is an arch-specific char string, that can be used + to identify CPU (and associate it with a set of PMU events + it supports). Multiple CPUIDS can point to the same + File/path/name.json. + + Example: + CPUID == 'GenuineIntel-6-2E' (on x86). + CPUID == '004b0100' (PVR value in Powerpc) + Version: + is the Version of the mapfile. + + Dir/path/name: + is the pathname to the directory containing the CPU's JSON + files, relative to the directory containing the mapfile.csv + + Type: + indicates whether the events or "core" or "uncore" events. + + + Eg: + + $ grep Silvermont tools/perf/pmu-events/arch/x86/mapfile.csv + GenuineIntel-6-37,V13,Silvermont_core,core + GenuineIntel-6-4D,V13,Silvermont_core,core + GenuineIntel-6-4C,V13,Silvermont_core,core + + i.e the three CPU models use the JSON files (i.e PMU events) listed + in the directory 'tools/perf/pmu-events/arch/x86/Silvermont_core'. diff --git a/tools/perf/pmu-events/jevents.c b/tools/perf/pmu-events/jevents.c new file mode 100644 index 000000000000..79c2133bc534 --- /dev/null +++ b/tools/perf/pmu-events/jevents.c @@ -0,0 +1,812 @@ +#define _XOPEN_SOURCE 500 /* needed for nftw() */ +#define _GNU_SOURCE /* needed for asprintf() */ + +/* Parse event JSON files */ + +/* + * Copyright (c) 2014, Intel Corporation + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <errno.h> +#include <string.h> +#include <ctype.h> +#include <unistd.h> +#include <stdarg.h> +#include <libgen.h> +#include <dirent.h> +#include <sys/time.h> /* getrlimit */ +#include <sys/resource.h> /* getrlimit */ +#include <ftw.h> +#include <sys/stat.h> +#include "jsmn.h" +#include "json.h" +#include "jevents.h" + +#ifndef __maybe_unused +#define __maybe_unused __attribute__((unused)) +#endif + +int verbose; +char *prog; + +int eprintf(int level, int var, const char *fmt, ...) +{ + + int ret; + va_list args; + + if (var < level) + return 0; + + va_start(args, fmt); + + ret = vfprintf(stderr, fmt, args); + + va_end(args); + + return ret; +} + +__attribute__((weak)) char *get_cpu_str(void) +{ + return NULL; +} + +static void addfield(char *map, char **dst, const char *sep, + const char *a, jsmntok_t *bt) +{ + unsigned int len = strlen(a) + 1 + strlen(sep); + int olen = *dst ? strlen(*dst) : 0; + int blen = bt ? json_len(bt) : 0; + char *out; + + out = realloc(*dst, len + olen + blen); + if (!out) { + /* Don't add field in this case */ + return; + } + *dst = out; + + if (!olen) + *(*dst) = 0; + else + strcat(*dst, sep); + strcat(*dst, a); + if (bt) + strncat(*dst, map + bt->start, blen); +} + +static void fixname(char *s) +{ + for (; *s; s++) + *s = tolower(*s); +} + +static void fixdesc(char *s) +{ + char *e = s + strlen(s); + + /* Remove trailing dots that look ugly in perf list */ + --e; + while (e >= s && isspace(*e)) + --e; + if (*e == '.') + *e = 0; +} + +static struct msrmap { + const char *num; + const char *pname; +} msrmap[] = { + { "0x3F6", "ldlat=" }, + { "0x1A6", "offcore_rsp=" }, + { "0x1A7", "offcore_rsp=" }, + { "0x3F7", "frontend=" }, + { NULL, NULL } +}; + +static struct field { + const char *field; + const char *kernel; +} fields[] = { + { "EventCode", "event=" }, + { "UMask", "umask=" }, + { "CounterMask", "cmask=" }, + { "Invert", "inv=" }, + { "AnyThread", "any=" }, + { "EdgeDetect", "edge=" }, + { "SampleAfterValue", "period=" }, + { NULL, NULL } +}; + +static void cut_comma(char *map, jsmntok_t *newval) +{ + int i; + + /* Cut off everything after comma */ + for (i = newval->start; i < newval->end; i++) { + if (map[i] == ',') + newval->end = i; + } +} + +static int match_field(char *map, jsmntok_t *field, int nz, + char **event, jsmntok_t *val) +{ + struct field *f; + jsmntok_t newval = *val; + + for (f = fields; f->field; f++) + if (json_streq(map, field, f->field) && nz) { + cut_comma(map, &newval); + addfield(map, event, ",", f->kernel, &newval); + return 1; + } + return 0; +} + +static struct msrmap *lookup_msr(char *map, jsmntok_t *val) +{ + jsmntok_t newval = *val; + static bool warned; + int i; + + cut_comma(map, &newval); + for (i = 0; msrmap[i].num; i++) + if (json_streq(map, &newval, msrmap[i].num)) + return &msrmap[i]; + if (!warned) { + warned = true; + pr_err("%s: Unknown MSR in event file %.*s\n", prog, + json_len(val), map + val->start); + } + return NULL; +} + +#define EXPECT(e, t, m) do { if (!(e)) { \ + jsmntok_t *loc = (t); \ + if (!(t)->start && (t) > tokens) \ + loc = (t) - 1; \ + pr_err("%s:%d: " m ", got %s\n", fn, \ + json_line(map, loc), \ + json_name(t)); \ + goto out_free; \ +} } while (0) + +#define TOPIC_DEPTH 256 +static char *topic_array[TOPIC_DEPTH]; +static int topic_level; + +static char *get_topic(void) +{ + char *tp_old, *tp = NULL; + int i; + + for (i = 0; i < topic_level + 1; i++) { + int n; + + tp_old = tp; + n = asprintf(&tp, "%s%s", tp ?: "", topic_array[i]); + if (n < 0) { + pr_info("%s: asprintf() error %s\n", prog); + return NULL; + } + free(tp_old); + } + + for (i = 0; i < (int) strlen(tp); i++) { + char c = tp[i]; + + if (c == '-') + tp[i] = ' '; + else if (c == '.') { + tp[i] = '\0'; + break; + } + } + + return tp; +} + +static int add_topic(int level, char *bname) +{ + char *topic; + + level -= 2; + + if (level >= TOPIC_DEPTH) + return -EINVAL; + + topic = strdup(bname); + if (!topic) { + pr_info("%s: strdup() error %s for file %s\n", prog, + strerror(errno), bname); + return -ENOMEM; + } + + free(topic_array[topic_level]); + topic_array[topic_level] = topic; + topic_level = level; + return 0; +} + +struct perf_entry_data { + FILE *outfp; + char *topic; +}; + +static int close_table; + +static void print_events_table_prefix(FILE *fp, const char *tblname) +{ + fprintf(fp, "struct pmu_event %s[] = {\n", tblname); + close_table = 1; +} + +static int print_events_table_entry(void *data, char *name, char *event, + char *desc, char *long_desc) +{ + struct perf_entry_data *pd = data; + FILE *outfp = pd->outfp; + char *topic = pd->topic; + + /* + * TODO: Remove formatting chars after debugging to reduce + * string lengths. + */ + fprintf(outfp, "{\n"); + + fprintf(outfp, "\t.name = \"%s\",\n", name); + fprintf(outfp, "\t.event = \"%s\",\n", event); + fprintf(outfp, "\t.desc = \"%s\",\n", desc); + fprintf(outfp, "\t.topic = \"%s\",\n", topic); + if (long_desc && long_desc[0]) + fprintf(outfp, "\t.long_desc = \"%s\",\n", long_desc); + + fprintf(outfp, "},\n"); + + return 0; +} + +static void print_events_table_suffix(FILE *outfp) +{ + fprintf(outfp, "{\n"); + + fprintf(outfp, "\t.name = 0,\n"); + fprintf(outfp, "\t.event = 0,\n"); + fprintf(outfp, "\t.desc = 0,\n"); + + fprintf(outfp, "},\n"); + fprintf(outfp, "};\n"); + close_table = 0; +} + +static struct fixed { + const char *name; + const char *event; +} fixed[] = { + { "inst_retired.any", "event=0xc0" }, + { "cpu_clk_unhalted.thread", "event=0x3c" }, + { "cpu_clk_unhalted.thread_any", "event=0x3c,any=1" }, + { NULL, NULL}, +}; + +/* + * Handle different fixed counter encodings between JSON and perf. + */ +static char *real_event(const char *name, char *event) +{ + int i; + + for (i = 0; fixed[i].name; i++) + if (!strcasecmp(name, fixed[i].name)) + return (char *)fixed[i].event; + return event; +} + +/* Call func with each event in the json file */ +int json_events(const char *fn, + int (*func)(void *data, char *name, char *event, char *desc, + char *long_desc), + void *data) +{ + int err = -EIO; + size_t size; + jsmntok_t *tokens, *tok; + int i, j, len; + char *map; + + if (!fn) + return -ENOENT; + + tokens = parse_json(fn, &map, &size, &len); + if (!tokens) + return -EIO; + EXPECT(tokens->type == JSMN_ARRAY, tokens, "expected top level array"); + tok = tokens + 1; + for (i = 0; i < tokens->size; i++) { + char *event = NULL, *desc = NULL, *name = NULL; + char *long_desc = NULL; + char *extra_desc = NULL; + struct msrmap *msr = NULL; + jsmntok_t *msrval = NULL; + jsmntok_t *precise = NULL; + jsmntok_t *obj = tok++; + + EXPECT(obj->type == JSMN_OBJECT, obj, "expected object"); + for (j = 0; j < obj->size; j += 2) { + jsmntok_t *field, *val; + int nz; + + field = tok + j; + EXPECT(field->type == JSMN_STRING, tok + j, + "Expected field name"); + val = tok + j + 1; + EXPECT(val->type == JSMN_STRING, tok + j + 1, + "Expected string value"); + + nz = !json_streq(map, val, "0"); + if (match_field(map, field, nz, &event, val)) { + /* ok */ + } else if (json_streq(map, field, "EventName")) { + addfield(map, &name, "", "", val); + } else if (json_streq(map, field, "BriefDescription")) { + addfield(map, &desc, "", "", val); + fixdesc(desc); + } else if (json_streq(map, field, + "PublicDescription")) { + addfield(map, &long_desc, "", "", val); + fixdesc(long_desc); + } else if (json_streq(map, field, "PEBS") && nz) { + precise = val; + } else if (json_streq(map, field, "MSRIndex") && nz) { + msr = lookup_msr(map, val); + } else if (json_streq(map, field, "MSRValue")) { + msrval = val; + } else if (json_streq(map, field, "Errata") && + !json_streq(map, val, "null")) { + addfield(map, &extra_desc, ". ", + " Spec update: ", val); + } else if (json_streq(map, field, "Data_LA") && nz) { + addfield(map, &extra_desc, ". ", + " Supports address when precise", + NULL); + } + /* ignore unknown fields */ + } + if (precise && desc && !strstr(desc, "(Precise Event)")) { + if (json_streq(map, precise, "2")) + addfield(map, &extra_desc, " ", + "(Must be precise)", NULL); + else + addfield(map, &extra_desc, " ", + "(Precise event)", NULL); + } + if (desc && extra_desc) + addfield(map, &desc, " ", extra_desc, NULL); + if (long_desc && extra_desc) + addfield(map, &long_desc, " ", extra_desc, NULL); + if (msr != NULL) + addfield(map, &event, ",", msr->pname, msrval); + fixname(name); + + err = func(data, name, real_event(name, event), desc, long_desc); + free(event); + free(desc); + free(name); + free(long_desc); + free(extra_desc); + if (err) + break; + tok += j; + } + EXPECT(tok - tokens == len, tok, "unexpected objects at end"); + err = 0; +out_free: + free_json(map, size, tokens); + return err; +} + +static char *file_name_to_table_name(char *fname) +{ + unsigned int i; + int n; + int c; + char *tblname; + + /* + * Ensure tablename starts with alphabetic character. + * Derive rest of table name from basename of the JSON file, + * replacing hyphens and stripping out .json suffix. + */ + n = asprintf(&tblname, "pme_%s", basename(fname)); + if (n < 0) { + pr_info("%s: asprintf() error %s for file %s\n", prog, + strerror(errno), fname); + return NULL; + } + + for (i = 0; i < strlen(tblname); i++) { + c = tblname[i]; + + if (c == '-') + tblname[i] = '_'; + else if (c == '.') { + tblname[i] = '\0'; + break; + } else if (!isalnum(c) && c != '_') { + pr_err("%s: Invalid character '%c' in file name %s\n", + prog, c, basename(fname)); + free(tblname); + tblname = NULL; + break; + } + } + + return tblname; +} + +static void print_mapping_table_prefix(FILE *outfp) +{ + fprintf(outfp, "struct pmu_events_map pmu_events_map[] = {\n"); +} + +static void print_mapping_table_suffix(FILE *outfp) +{ + /* + * Print the terminating, NULL entry. + */ + fprintf(outfp, "{\n"); + fprintf(outfp, "\t.cpuid = 0,\n"); + fprintf(outfp, "\t.version = 0,\n"); + fprintf(outfp, "\t.type = 0,\n"); + fprintf(outfp, "\t.table = 0,\n"); + fprintf(outfp, "},\n"); + + /* and finally, the closing curly bracket for the struct */ + fprintf(outfp, "};\n"); +} + +static int process_mapfile(FILE *outfp, char *fpath) +{ + int n = 16384; + FILE *mapfp; + char *save = NULL; + char *line, *p; + int line_num; + char *tblname; + + pr_info("%s: Processing mapfile %s\n", prog, fpath); + + line = malloc(n); + if (!line) + return -1; + + mapfp = fopen(fpath, "r"); + if (!mapfp) { + pr_info("%s: Error %s opening %s\n", prog, strerror(errno), + fpath); + return -1; + } + + print_mapping_table_prefix(outfp); + + /* Skip first line (header) */ + p = fgets(line, n, mapfp); + if (!p) + goto out; + + line_num = 1; + while (1) { + char *cpuid, *version, *type, *fname; + + line_num++; + p = fgets(line, n, mapfp); + if (!p) + break; + + if (line[0] == '#' || line[0] == '\n') + continue; + + if (line[strlen(line)-1] != '\n') { + /* TODO Deal with lines longer than 16K */ + pr_info("%s: Mapfile %s: line %d too long, aborting\n", + prog, fpath, line_num); + return -1; + } + line[strlen(line)-1] = '\0'; + + cpuid = strtok_r(p, ",", &save); + version = strtok_r(NULL, ",", &save); + fname = strtok_r(NULL, ",", &save); + type = strtok_r(NULL, ",", &save); + + tblname = file_name_to_table_name(fname); + fprintf(outfp, "{\n"); + fprintf(outfp, "\t.cpuid = \"%s\",\n", cpuid); + fprintf(outfp, "\t.version = \"%s\",\n", version); + fprintf(outfp, "\t.type = \"%s\",\n", type); + + /* + * CHECK: We can't use the type (eg "core") field in the + * table name. For us to do that, we need to somehow tweak + * the other caller of file_name_to_table(), process_json() + * to determine the type. process_json() file has no way + * of knowing these are "core" events unless file name has + * core in it. If filename has core in it, we can safely + * ignore the type field here also. + */ + fprintf(outfp, "\t.table = %s\n", tblname); + fprintf(outfp, "},\n"); + } + +out: + print_mapping_table_suffix(outfp); + return 0; +} + +/* + * If we fail to locate/process JSON and map files, create a NULL mapping + * table. This would at least allow perf to build even if we can't find/use + * the aliases. + */ +static void create_empty_mapping(const char *output_file) +{ + FILE *outfp; + + pr_info("%s: Creating empty pmu_events_map[] table\n", prog); + + /* Truncate file to clear any partial writes to it */ + outfp = fopen(output_file, "w"); + if (!outfp) { + perror("fopen()"); + _Exit(1); + } + + fprintf(outfp, "#include \"../../pmu-events/pmu-events.h\"\n"); + print_mapping_table_prefix(outfp); + print_mapping_table_suffix(outfp); + fclose(outfp); +} + +static int get_maxfds(void) +{ + struct rlimit rlim; + + if (getrlimit(RLIMIT_NOFILE, &rlim) == 0) + return min((int)rlim.rlim_max / 2, 512); + + return 512; +} + +/* + * nftw() doesn't let us pass an argument to the processing function, + * so use a global variables. + */ +static FILE *eventsfp; +static char *mapfile; + +static int process_one_file(const char *fpath, const struct stat *sb, + int typeflag, struct FTW *ftwbuf) +{ + char *tblname, *bname = (char *) fpath + ftwbuf->base; + int is_dir = typeflag == FTW_D; + int is_file = typeflag == FTW_F; + int level = ftwbuf->level; + int err = 0; + + pr_debug("%s %d %7jd %-20s %s\n", + is_file ? "f" : is_dir ? "d" : "x", + level, sb->st_size, bname, fpath); + + /* base dir */ + if (level == 0) + return 0; + + /* model directory, reset topic */ + if (level == 1 && is_dir) { + if (close_table) + print_events_table_suffix(eventsfp); + + /* + * Drop file name suffix. Replace hyphens with underscores. + * Fail if file name contains any alphanum characters besides + * underscores. + */ + tblname = file_name_to_table_name(bname); + if (!tblname) { + pr_info("%s: Error determining table name for %s\n", prog, + bname); + return -1; + } + + print_events_table_prefix(eventsfp, tblname); + return 0; + } + + /* + * Save the mapfile name for now. We will process mapfile + * after processing all JSON files (so we can write out the + * mapping table after all PMU events tables). + * + * TODO: Allow for multiple mapfiles? Punt for now. + */ + if (level == 1 && is_file) { + if (!strncmp(bname, "mapfile.csv", 11)) { + if (mapfile) { + pr_info("%s: Many mapfiles? Using %s, ignoring %s\n", + prog, mapfile, fpath); + } else { + mapfile = strdup(fpath); + } + return 0; + } + + pr_info("%s: Ignoring file %s\n", prog, fpath); + return 0; + } + + /* + * If the file name does not have a .json extension, + * ignore it. It could be a readme.txt for instance. + */ + if (is_file) { + char *suffix = bname + strlen(bname) - 5; + + if (strncmp(suffix, ".json", 5)) { + pr_info("%s: Ignoring file without .json suffix %s\n", prog, + fpath); + return 0; + } + } + + if (level > 1 && add_topic(level, bname)) + return -ENOMEM; + + /* + * Assume all other files are JSON files. + * + * If mapfile refers to 'power7_core.json', we create a table + * named 'power7_core'. Any inconsistencies between the mapfile + * and directory tree could result in build failure due to table + * names not being found. + * + * Atleast for now, be strict with processing JSON file names. + * i.e. if JSON file name cannot be mapped to C-style table name, + * fail. + */ + if (is_file) { + struct perf_entry_data data = { + .topic = get_topic(), + .outfp = eventsfp, + }; + + err = json_events(fpath, print_events_table_entry, &data); + + free(data.topic); + } + + return err; +} + +#ifndef PATH_MAX +#define PATH_MAX 4096 +#endif + +/* + * Starting in directory 'start_dirname', find the "mapfile.csv" and + * the set of JSON files for the architecture 'arch'. + * + * From each JSON file, create a C-style "PMU events table" from the + * JSON file (see struct pmu_event). + * + * From the mapfile, create a mapping between the CPU revisions and + * PMU event tables (see struct pmu_events_map). + * + * Write out the PMU events tables and the mapping table to pmu-event.c. + * + * If unable to process the JSON or arch files, create an empty mapping + * table so we can continue to build/use perf even if we cannot use the + * PMU event aliases. + */ +int main(int argc, char *argv[]) +{ + int rc; + int maxfds; + char ldirname[PATH_MAX]; + + const char *arch; + const char *output_file; + const char *start_dirname; + + prog = basename(argv[0]); + if (argc < 4) { + pr_err("Usage: %s <arch> <starting_dir> <output_file>\n", prog); + return 1; + } + + arch = argv[1]; + start_dirname = argv[2]; + output_file = argv[3]; + + if (argc > 4) + verbose = atoi(argv[4]); + + eventsfp = fopen(output_file, "w"); + if (!eventsfp) { + pr_err("%s Unable to create required file %s (%s)\n", + prog, output_file, strerror(errno)); + return 2; + } + + /* Include pmu-events.h first */ + fprintf(eventsfp, "#include \"../../pmu-events/pmu-events.h\"\n"); + + sprintf(ldirname, "%s/%s", start_dirname, arch); + + /* + * The mapfile allows multiple CPUids to point to the same JSON file, + * so, not sure if there is a need for symlinks within the pmu-events + * directory. + * + * For now, treat symlinks of JSON files as regular files and create + * separate tables for each symlink (presumably, each symlink refers + * to specific version of the CPU). + */ + + maxfds = get_maxfds(); + mapfile = NULL; + rc = nftw(ldirname, process_one_file, maxfds, 0); + if (rc && verbose) { + pr_info("%s: Error walking file tree %s\n", prog, ldirname); + goto empty_map; + } else if (rc) { + goto empty_map; + } + + if (close_table) + print_events_table_suffix(eventsfp); + + if (!mapfile) { + pr_info("%s: No CPU->JSON mapping?\n", prog); + goto empty_map; + } + + if (process_mapfile(eventsfp, mapfile)) { + pr_info("%s: Error processing mapfile %s\n", prog, mapfile); + goto empty_map; + } + + return 0; + +empty_map: + fclose(eventsfp); + create_empty_mapping(output_file); + return 0; +} diff --git a/tools/perf/pmu-events/jevents.h b/tools/perf/pmu-events/jevents.h new file mode 100644 index 000000000000..b0eb2744b498 --- /dev/null +++ b/tools/perf/pmu-events/jevents.h @@ -0,0 +1,18 @@ +#ifndef JEVENTS_H +#define JEVENTS_H 1 + +int json_events(const char *fn, + int (*func)(void *data, char *name, char *event, char *desc, + char *long_desc), + void *data); +char *get_cpu_str(void); + +#ifndef min +#define min(x, y) ({ \ + typeof(x) _min1 = (x); \ + typeof(y) _min2 = (y); \ + (void) (&_min1 == &_min2); \ + _min1 < _min2 ? _min1 : _min2; }) +#endif + +#endif diff --git a/tools/perf/pmu-events/jsmn.c b/tools/perf/pmu-events/jsmn.c new file mode 100644 index 000000000000..11d1fa18bfa5 --- /dev/null +++ b/tools/perf/pmu-events/jsmn.c @@ -0,0 +1,313 @@ +/* + * Copyright (c) 2010 Serge A. Zaitsev + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * Slightly modified by AK to not assume 0 terminated input. + */ + +#include <stdlib.h> +#include "jsmn.h" + +/* + * Allocates a fresh unused token from the token pool. + */ +static jsmntok_t *jsmn_alloc_token(jsmn_parser *parser, + jsmntok_t *tokens, size_t num_tokens) +{ + jsmntok_t *tok; + + if ((unsigned)parser->toknext >= num_tokens) + return NULL; + tok = &tokens[parser->toknext++]; + tok->start = tok->end = -1; + tok->size = 0; + return tok; +} + +/* + * Fills token type and boundaries. + */ +static void jsmn_fill_token(jsmntok_t *token, jsmntype_t type, + int start, int end) +{ + token->type = type; + token->start = start; + token->end = end; + token->size = 0; +} + +/* + * Fills next available token with JSON primitive. + */ +static jsmnerr_t jsmn_parse_primitive(jsmn_parser *parser, const char *js, + size_t len, + jsmntok_t *tokens, size_t num_tokens) +{ + jsmntok_t *token; + int start; + + start = parser->pos; + + for (; parser->pos < len; parser->pos++) { + switch (js[parser->pos]) { +#ifndef JSMN_STRICT + /* + * In strict mode primitive must be followed by "," + * or "}" or "]" + */ + case ':': +#endif + case '\t': + case '\r': + case '\n': + case ' ': + case ',': + case ']': + case '}': + goto found; + default: + break; + } + if (js[parser->pos] < 32 || js[parser->pos] >= 127) { + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } +#ifdef JSMN_STRICT + /* + * In strict mode primitive must be followed by a + * comma/object/array. + */ + parser->pos = start; + return JSMN_ERROR_PART; +#endif + +found: + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + parser->pos = start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_PRIMITIVE, start, parser->pos); + parser->pos--; /* parent sees closing brackets */ + return JSMN_SUCCESS; +} + +/* + * Fills next token with JSON string. + */ +static jsmnerr_t jsmn_parse_string(jsmn_parser *parser, const char *js, + size_t len, + jsmntok_t *tokens, size_t num_tokens) +{ + jsmntok_t *token; + int start = parser->pos; + + /* Skip starting quote */ + parser->pos++; + + for (; parser->pos < len; parser->pos++) { + char c = js[parser->pos]; + + /* Quote: end of string */ + if (c == '\"') { + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) { + parser->pos = start; + return JSMN_ERROR_NOMEM; + } + jsmn_fill_token(token, JSMN_STRING, start+1, + parser->pos); + return JSMN_SUCCESS; + } + + /* Backslash: Quoted symbol expected */ + if (c == '\\') { + parser->pos++; + switch (js[parser->pos]) { + /* Allowed escaped symbols */ + case '\"': + case '/': + case '\\': + case 'b': + case 'f': + case 'r': + case 'n': + case 't': + break; + /* Allows escaped symbol \uXXXX */ + case 'u': + /* TODO */ + break; + /* Unexpected symbol */ + default: + parser->pos = start; + return JSMN_ERROR_INVAL; + } + } + } + parser->pos = start; + return JSMN_ERROR_PART; +} + +/* + * Parse JSON string and fill tokens. + */ +jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, size_t len, + jsmntok_t *tokens, unsigned int num_tokens) +{ + jsmnerr_t r; + int i; + jsmntok_t *token; + + for (; parser->pos < len; parser->pos++) { + char c; + jsmntype_t type; + + c = js[parser->pos]; + switch (c) { + case '{': + case '[': + token = jsmn_alloc_token(parser, tokens, num_tokens); + if (token == NULL) + return JSMN_ERROR_NOMEM; + if (parser->toksuper != -1) + tokens[parser->toksuper].size++; + token->type = (c == '{' ? JSMN_OBJECT : JSMN_ARRAY); + token->start = parser->pos; + parser->toksuper = parser->toknext - 1; + break; + case '}': + case ']': + type = (c == '}' ? JSMN_OBJECT : JSMN_ARRAY); + for (i = parser->toknext - 1; i >= 0; i--) { + token = &tokens[i]; + if (token->start != -1 && token->end == -1) { + if (token->type != type) + return JSMN_ERROR_INVAL; + parser->toksuper = -1; + token->end = parser->pos + 1; + break; + } + } + /* Error if unmatched closing bracket */ + if (i == -1) + return JSMN_ERROR_INVAL; + for (; i >= 0; i--) { + token = &tokens[i]; + if (token->start != -1 && token->end == -1) { + parser->toksuper = i; + break; + } + } + break; + case '\"': + r = jsmn_parse_string(parser, js, len, tokens, + num_tokens); + if (r < 0) + return r; + if (parser->toksuper != -1) + tokens[parser->toksuper].size++; + break; + case '\t': + case '\r': + case '\n': + case ':': + case ',': + case ' ': + break; +#ifdef JSMN_STRICT + /* + * In strict mode primitives are: + * numbers and booleans. + */ + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 't': + case 'f': + case 'n': +#else + /* + * In non-strict mode every unquoted value + * is a primitive. + */ + /*FALL THROUGH */ + default: +#endif + r = jsmn_parse_primitive(parser, js, len, tokens, + num_tokens); + if (r < 0) + return r; + if (parser->toksuper != -1) + tokens[parser->toksuper].size++; + break; + +#ifdef JSMN_STRICT + /* Unexpected char in strict mode */ + default: + return JSMN_ERROR_INVAL; +#endif + } + } + + for (i = parser->toknext - 1; i >= 0; i--) { + /* Unmatched opened object or array */ + if (tokens[i].start != -1 && tokens[i].end == -1) + return JSMN_ERROR_PART; + } + + return JSMN_SUCCESS; +} + +/* + * Creates a new parser based over a given buffer with an array of tokens + * available. + */ +void jsmn_init(jsmn_parser *parser) +{ + parser->pos = 0; + parser->toknext = 0; + parser->toksuper = -1; +} + +const char *jsmn_strerror(jsmnerr_t err) +{ + switch (err) { + case JSMN_ERROR_NOMEM: + return "No enough tokens"; + case JSMN_ERROR_INVAL: + return "Invalid character inside JSON string"; + case JSMN_ERROR_PART: + return "The string is not a full JSON packet, more bytes expected"; + case JSMN_SUCCESS: + return "Success"; + default: + return "Unknown json error"; + } +} diff --git a/tools/perf/pmu-events/jsmn.h b/tools/perf/pmu-events/jsmn.h new file mode 100644 index 000000000000..d666b10cf25b --- /dev/null +++ b/tools/perf/pmu-events/jsmn.h @@ -0,0 +1,67 @@ +#ifndef __JSMN_H_ +#define __JSMN_H_ + +/* + * JSON type identifier. Basic types are: + * o Object + * o Array + * o String + * o Other primitive: number, boolean (true/false) or null + */ +typedef enum { + JSMN_PRIMITIVE = 0, + JSMN_OBJECT = 1, + JSMN_ARRAY = 2, + JSMN_STRING = 3 +} jsmntype_t; + +typedef enum { + /* Not enough tokens were provided */ + JSMN_ERROR_NOMEM = -1, + /* Invalid character inside JSON string */ + JSMN_ERROR_INVAL = -2, + /* The string is not a full JSON packet, more bytes expected */ + JSMN_ERROR_PART = -3, + /* Everything was fine */ + JSMN_SUCCESS = 0 +} jsmnerr_t; + +/* + * JSON token description. + * @param type type (object, array, string etc.) + * @param start start position in JSON data string + * @param end end position in JSON data string + */ +typedef struct { + jsmntype_t type; + int start; + int end; + int size; +} jsmntok_t; + +/* + * JSON parser. Contains an array of token blocks available. Also stores + * the string being parsed now and current position in that string + */ +typedef struct { + unsigned int pos; /* offset in the JSON string */ + int toknext; /* next token to allocate */ + int toksuper; /* superior token node, e.g parent object or array */ +} jsmn_parser; + +/* + * Create JSON parser over an array of tokens + */ +void jsmn_init(jsmn_parser *parser); + +/* + * Run JSON parser. It parses a JSON data string into and array of tokens, + * each describing a single JSON object. + */ +jsmnerr_t jsmn_parse(jsmn_parser *parser, const char *js, + size_t len, + jsmntok_t *tokens, unsigned int num_tokens); + +const char *jsmn_strerror(jsmnerr_t err); + +#endif /* __JSMN_H_ */ diff --git a/tools/perf/pmu-events/json.c b/tools/perf/pmu-events/json.c new file mode 100644 index 000000000000..f67bbb0aa36e --- /dev/null +++ b/tools/perf/pmu-events/json.c @@ -0,0 +1,162 @@ +/* Parse JSON files using the JSMN parser. */ + +/* + * Copyright (c) 2014, Intel Corporation + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#include <stdlib.h> +#include <string.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <stdio.h> +#include <errno.h> +#include <unistd.h> +#include "jsmn.h" +#include "json.h" +#include <linux/kernel.h> + + +static char *mapfile(const char *fn, size_t *size) +{ + unsigned ps = sysconf(_SC_PAGESIZE); + struct stat st; + char *map = NULL; + int err; + int fd = open(fn, O_RDONLY); + + if (fd < 0 && verbose && fn) { + pr_err("Error opening events file '%s': %s\n", fn, + strerror(errno)); + } + + if (fd < 0) + return NULL; + err = fstat(fd, &st); + if (err < 0) + goto out; + *size = st.st_size; + map = mmap(NULL, + (st.st_size + ps - 1) & ~(ps - 1), + PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0); + if (map == MAP_FAILED) + map = NULL; +out: + close(fd); + return map; +} + +static void unmapfile(char *map, size_t size) +{ + unsigned ps = sysconf(_SC_PAGESIZE); + munmap(map, roundup(size, ps)); +} + +/* + * Parse json file using jsmn. Return array of tokens, + * and mapped file. Caller needs to free array. + */ +jsmntok_t *parse_json(const char *fn, char **map, size_t *size, int *len) +{ + jsmn_parser parser; + jsmntok_t *tokens; + jsmnerr_t res; + unsigned sz; + + *map = mapfile(fn, size); + if (!*map) + return NULL; + /* Heuristic */ + sz = *size * 16; + tokens = malloc(sz); + if (!tokens) + goto error; + jsmn_init(&parser); + res = jsmn_parse(&parser, *map, *size, tokens, + sz / sizeof(jsmntok_t)); + if (res != JSMN_SUCCESS) { + pr_err("%s: json error %s\n", fn, jsmn_strerror(res)); + goto error_free; + } + if (len) + *len = parser.toknext; + return tokens; +error_free: + free(tokens); +error: + unmapfile(*map, *size); + return NULL; +} + +void free_json(char *map, size_t size, jsmntok_t *tokens) +{ + free(tokens); + unmapfile(map, size); +} + +static int countchar(char *map, char c, int end) +{ + int i; + int count = 0; + for (i = 0; i < end; i++) + if (map[i] == c) + count++; + return count; +} + +/* Return line number of a jsmn token */ +int json_line(char *map, jsmntok_t *t) +{ + return countchar(map, '\n', t->start) + 1; +} + +static const char * const jsmn_types[] = { + [JSMN_PRIMITIVE] = "primitive", + [JSMN_ARRAY] = "array", + [JSMN_OBJECT] = "object", + [JSMN_STRING] = "string" +}; + +#define LOOKUP(a, i) ((i) < (sizeof(a)/sizeof(*(a))) ? ((a)[i]) : "?") + +/* Return type name of a jsmn token */ +const char *json_name(jsmntok_t *t) +{ + return LOOKUP(jsmn_types, t->type); +} + +int json_len(jsmntok_t *t) +{ + return t->end - t->start; +} + +/* Is string t equal to s? */ +int json_streq(char *map, jsmntok_t *t, const char *s) +{ + unsigned len = json_len(t); + return len == strlen(s) && !strncasecmp(map + t->start, s, len); +} diff --git a/tools/perf/pmu-events/json.h b/tools/perf/pmu-events/json.h new file mode 100644 index 000000000000..278ebd32cfb6 --- /dev/null +++ b/tools/perf/pmu-events/json.h @@ -0,0 +1,38 @@ +#ifndef JSON_H +#define JSON_H 1 + +#include "jsmn.h" + +jsmntok_t *parse_json(const char *fn, char **map, size_t *size, int *len); +void free_json(char *map, size_t size, jsmntok_t *tokens); +int json_line(char *map, jsmntok_t *t); +const char *json_name(jsmntok_t *t); +int json_streq(char *map, jsmntok_t *t, const char *s); +int json_len(jsmntok_t *t); + +extern int verbose; + +#include <stdbool.h> + +extern int eprintf(int level, int var, const char *fmt, ...); +#define pr_fmt(fmt) fmt + +#define pr_err(fmt, ...) \ + eprintf(0, verbose, pr_fmt(fmt), ##__VA_ARGS__) + +#define pr_info(fmt, ...) \ + eprintf(1, verbose, pr_fmt(fmt), ##__VA_ARGS__) + +#define pr_debug(fmt, ...) \ + eprintf(2, verbose, pr_fmt(fmt), ##__VA_ARGS__) + +#ifndef roundup +#define roundup(x, y) ( \ +{ \ + const typeof(y) __y = y; \ + (((x) + (__y - 1)) / __y) * __y; \ +} \ +) +#endif + +#endif diff --git a/tools/perf/pmu-events/pmu-events.h b/tools/perf/pmu-events/pmu-events.h new file mode 100644 index 000000000000..2eaef595d8a0 --- /dev/null +++ b/tools/perf/pmu-events/pmu-events.h @@ -0,0 +1,37 @@ +#ifndef PMU_EVENTS_H +#define PMU_EVENTS_H + +/* + * Describe each PMU event. Each CPU has a table of PMU events. + */ +struct pmu_event { + const char *name; + const char *event; + const char *desc; + const char *topic; + const char *long_desc; +}; + +/* + * + * Map a CPU to its table of PMU events. The CPU is identified by the + * cpuid field, which is an arch-specific identifier for the CPU. + * The identifier specified in tools/perf/pmu-events/arch/xxx/mapfile + * must match the get_cpustr() in tools/perf/arch/xxx/util/header.c) + * + * The cpuid can contain any character other than the comma. + */ +struct pmu_events_map { + const char *cpuid; + const char *version; + const char *type; /* core, uncore etc */ + struct pmu_event *table; +}; + +/* + * Global table mapping each known CPU for the architecture to its + * table of PMU events. + */ +extern struct pmu_events_map pmu_events_map[]; + +#endif diff --git a/tools/perf/util/evlist.c b/tools/perf/util/evlist.c index ea34c5a32c11..d92e02006fb8 100644 --- a/tools/perf/util/evlist.c +++ b/tools/perf/util/evlist.c @@ -384,15 +384,14 @@ void perf_evlist__toggle_enable(struct perf_evlist *evlist) static int perf_evlist__enable_event_cpu(struct perf_evlist *evlist, struct perf_evsel *evsel, int cpu) { - int thread, err; + int thread; int nr_threads = perf_evlist__nr_threads(evlist, evsel); if (!evsel->fd) return -EINVAL; for (thread = 0; thread < nr_threads; thread++) { - err = ioctl(FD(evsel, cpu, thread), - PERF_EVENT_IOC_ENABLE, 0); + int err = ioctl(FD(evsel, cpu, thread), PERF_EVENT_IOC_ENABLE, 0); if (err) return err; } @@ -403,14 +402,14 @@ static int perf_evlist__enable_event_thread(struct perf_evlist *evlist, struct perf_evsel *evsel, int thread) { - int cpu, err; + int cpu; int nr_cpus = cpu_map__nr(evlist->cpus); if (!evsel->fd) return -EINVAL; for (cpu = 0; cpu < nr_cpus; cpu++) { - err = ioctl(FD(evsel, cpu, thread), PERF_EVENT_IOC_ENABLE, 0); + int err = ioctl(FD(evsel, cpu, thread), PERF_EVENT_IOC_ENABLE, 0); if (err) return err; } @@ -1606,10 +1605,9 @@ void perf_evlist__close(struct perf_evlist *evlist) struct perf_evsel *evsel; int ncpus = cpu_map__nr(evlist->cpus); int nthreads = thread_map__nr(evlist->threads); - int n; evlist__for_each_entry_reverse(evlist, evsel) { - n = evsel->cpus ? evsel->cpus->nr : ncpus; + int n = evsel->cpus ? evsel->cpus->nr : ncpus; perf_evsel__close(evsel, n, nthreads); } } diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c index 380e84c3af3d..8bc271141d9d 100644 --- a/tools/perf/util/evsel.c +++ b/tools/perf/util/evsel.c @@ -985,14 +985,13 @@ void perf_evsel__config(struct perf_evsel *evsel, struct record_opts *opts, static int perf_evsel__alloc_fd(struct perf_evsel *evsel, int ncpus, int nthreads) { - int cpu, thread; - if (evsel->system_wide) nthreads = 1; evsel->fd = xyarray__new(ncpus, nthreads, sizeof(int)); if (evsel->fd) { + int cpu, thread; for (cpu = 0; cpu < ncpus; cpu++) { for (thread = 0; thread < nthreads; thread++) { FD(evsel, cpu, thread) = -1; diff --git a/tools/perf/util/header.h b/tools/perf/util/header.h index d306ca118449..d30109b421ee 100644 --- a/tools/perf/util/header.h +++ b/tools/perf/util/header.h @@ -151,4 +151,5 @@ int write_padded(int fd, const void *bf, size_t count, size_t count_aligned); */ int get_cpuid(char *buffer, size_t sz); +char *get_cpuid_str(void); #endif /* __PERF_HEADER_H */ diff --git a/tools/perf/util/machine.c b/tools/perf/util/machine.c index 18e4519abef2..df85b9efd80f 100644 --- a/tools/perf/util/machine.c +++ b/tools/perf/util/machine.c @@ -1745,9 +1745,8 @@ static int resolve_lbr_callchain_sample(struct thread *thread, int max_stack) { struct ip_callchain *chain = sample->callchain; - int chain_nr = min(max_stack, (int)chain->nr); + int chain_nr = min(max_stack, (int)chain->nr), i; u8 cpumode = PERF_RECORD_MISC_USER; - int i, j, err; u64 ip; for (i = 0; i < chain_nr; i++) { @@ -1758,7 +1757,7 @@ static int resolve_lbr_callchain_sample(struct thread *thread, /* LBR only affects the user callchain */ if (i != chain_nr) { struct branch_stack *lbr_stack = sample->branch_stack; - int lbr_nr = lbr_stack->nr; + int lbr_nr = lbr_stack->nr, j; /* * LBR callstack can only get user call chain. * The mix_chain_nr is kernel call chain @@ -1772,6 +1771,7 @@ static int resolve_lbr_callchain_sample(struct thread *thread, int mix_chain_nr = i + 1 + lbr_nr + 1; for (j = 0; j < mix_chain_nr; j++) { + int err; if (callchain_param.order == ORDER_CALLEE) { if (j < i + 1) ip = chain->ips[j]; diff --git a/tools/perf/util/parse-events.c b/tools/perf/util/parse-events.c index 33546c3ac1fe..4e778eae1510 100644 --- a/tools/perf/util/parse-events.c +++ b/tools/perf/util/parse-events.c @@ -924,6 +924,7 @@ config_term_avail(int term_type, struct parse_events_error *err) case PARSE_EVENTS__TERM_TYPE_CONFIG1: case PARSE_EVENTS__TERM_TYPE_CONFIG2: case PARSE_EVENTS__TERM_TYPE_NAME: + case PARSE_EVENTS__TERM_TYPE_SAMPLE_PERIOD: return true; default: if (!err) @@ -1458,7 +1459,7 @@ comp_pmu(const void *p1, const void *p2) struct perf_pmu_event_symbol *pmu1 = (struct perf_pmu_event_symbol *) p1; struct perf_pmu_event_symbol *pmu2 = (struct perf_pmu_event_symbol *) p2; - return strcmp(pmu1->symbol, pmu2->symbol); + return strcasecmp(pmu1->symbol, pmu2->symbol); } static void perf_pmu__parse_cleanup(void) @@ -2263,7 +2264,8 @@ out_enomem: /* * Print the help text for the event symbols: */ -void print_events(const char *event_glob, bool name_only) +void print_events(const char *event_glob, bool name_only, bool quiet_flag, + bool long_desc) { print_symbol_events(event_glob, PERF_TYPE_HARDWARE, event_symbols_hw, PERF_COUNT_HW_MAX, name_only); @@ -2273,7 +2275,7 @@ void print_events(const char *event_glob, bool name_only) print_hwcache_events(event_glob, name_only); - print_pmu_events(event_glob, name_only); + print_pmu_events(event_glob, name_only, quiet_flag, long_desc); if (event_glob != NULL) return; diff --git a/tools/perf/util/parse-events.h b/tools/perf/util/parse-events.h index 8d09a976fca8..da246a3ddb69 100644 --- a/tools/perf/util/parse-events.h +++ b/tools/perf/util/parse-events.h @@ -172,7 +172,8 @@ void parse_events_update_lists(struct list_head *list_event, void parse_events_evlist_error(struct parse_events_evlist *data, int idx, const char *str); -void print_events(const char *event_glob, bool name_only); +void print_events(const char *event_glob, bool name_only, bool quiet, + bool long_desc); struct event_symbol { const char *symbol; diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c index 2babcdf62839..b1474dcadfa2 100644 --- a/tools/perf/util/pmu.c +++ b/tools/perf/util/pmu.c @@ -12,6 +12,9 @@ #include "pmu.h" #include "parse-events.h" #include "cpumap.h" +#include "header.h" +#include "pmu-events/pmu-events.h" +#include "cache.h" struct perf_pmu_format { char *name; @@ -220,7 +223,8 @@ static int perf_pmu__parse_snapshot(struct perf_pmu_alias *alias, } static int __perf_pmu__new_alias(struct list_head *list, char *dir, char *name, - char *desc __maybe_unused, char *val) + char *desc, char *val, char *long_desc, + char *topic) { struct perf_pmu_alias *alias; int ret; @@ -253,6 +257,11 @@ static int __perf_pmu__new_alias(struct list_head *list, char *dir, char *name, perf_pmu__parse_snapshot(alias, dir, name); } + alias->desc = desc ? strdup(desc) : NULL; + alias->long_desc = long_desc ? strdup(long_desc) : + desc ? strdup(desc) : NULL; + alias->topic = topic ? strdup(topic) : NULL; + list_add_tail(&alias->list, list); return 0; @@ -269,7 +278,7 @@ static int perf_pmu__new_alias(struct list_head *list, char *dir, char *name, FI buf[ret] = 0; - return __perf_pmu__new_alias(list, dir, name, NULL, buf); + return __perf_pmu__new_alias(list, dir, name, NULL, buf, NULL, NULL); } static inline bool pmu_alias_info_file(char *name) @@ -473,6 +482,68 @@ static struct cpu_map *pmu_cpumask(const char *name) return cpus; } +/* + * Return the CPU id as a raw string. + * + * Each architecture should provide a more precise id string that + * can be use to match the architecture's "mapfile". + */ +char * __weak get_cpuid_str(void) +{ + return NULL; +} + +/* + * From the pmu_events_map, find the table of PMU events that corresponds + * to the current running CPU. Then, add all PMU events from that table + * as aliases. + */ +static void pmu_add_cpu_aliases(struct list_head *head) +{ + int i; + struct pmu_events_map *map; + struct pmu_event *pe; + char *cpuid; + + cpuid = getenv("PERF_CPUID"); + if (cpuid) + cpuid = strdup(cpuid); + if (!cpuid) + cpuid = get_cpuid_str(); + if (!cpuid) + return; + + pr_debug("Using CPUID %s\n", cpuid); + + i = 0; + while (1) { + map = &pmu_events_map[i++]; + if (!map->table) + goto out; + + if (!strcmp(map->cpuid, cpuid)) + break; + } + + /* + * Found a matching PMU events table. Create aliases + */ + i = 0; + while (1) { + pe = &map->table[i++]; + if (!pe->name) + break; + + /* need type casts to override 'const' */ + __perf_pmu__new_alias(head, NULL, (char *)pe->name, + (char *)pe->desc, (char *)pe->event, + (char *)pe->long_desc, (char *)pe->topic); + } + +out: + free(cpuid); +} + struct perf_event_attr * __weak perf_pmu__get_default_config(struct perf_pmu *pmu __maybe_unused) { @@ -497,6 +568,9 @@ static struct perf_pmu *pmu_lookup(const char *name) if (pmu_aliases(name, &aliases)) return NULL; + if (!strcmp(name, "cpu")) + pmu_add_cpu_aliases(&aliases); + if (pmu_type(name, &type)) return NULL; @@ -983,21 +1057,63 @@ static char *format_alias_or(char *buf, int len, struct perf_pmu *pmu, return buf; } -static int cmp_string(const void *a, const void *b) +struct sevent { + char *name; + char *desc; + char *topic; +}; + +static int cmp_sevent(const void *a, const void *b) { - const char * const *as = a; - const char * const *bs = b; - return strcmp(*as, *bs); + const struct sevent *as = a; + const struct sevent *bs = b; + + /* Put extra events last */ + if (!!as->desc != !!bs->desc) + return !!as->desc - !!bs->desc; + if (as->topic && bs->topic) { + int n = strcmp(as->topic, bs->topic); + + if (n) + return n; + } + return strcmp(as->name, bs->name); } -void print_pmu_events(const char *event_glob, bool name_only) +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; + while (isspace(*s)) + s++; + } +} + +void print_pmu_events(const char *event_glob, bool name_only, bool quiet_flag, + bool long_desc) { struct perf_pmu *pmu; struct perf_pmu_alias *alias; char buf[1024]; int printed = 0; int len, j; - char **aliases; + struct sevent *aliases; + int numdesc = 0; + int columns = pager_get_columns(); + char *topic = NULL; pmu = NULL; len = 0; @@ -1007,14 +1123,15 @@ void print_pmu_events(const char *event_glob, bool name_only) if (pmu->selectable) len++; } - aliases = zalloc(sizeof(char *) * len); + aliases = zalloc(sizeof(struct sevent) * len); if (!aliases) goto out_enomem; pmu = NULL; j = 0; while ((pmu = perf_pmu__scan(pmu)) != NULL) { list_for_each_entry(alias, &pmu->aliases, list) { - char *name = format_alias(buf, sizeof(buf), pmu, alias); + char *name = alias->desc ? alias->name : + format_alias(buf, sizeof(buf), pmu, alias); bool is_cpu = !strcmp(pmu->name, "cpu"); if (event_glob != NULL && @@ -1023,12 +1140,21 @@ void print_pmu_events(const char *event_glob, bool name_only) event_glob)))) continue; - if (is_cpu && !name_only) + if (is_cpu && !name_only && !alias->desc) name = format_alias_or(buf, sizeof(buf), pmu, alias); - aliases[j] = strdup(name); - if (aliases[j] == NULL) + aliases[j].name = name; + if (is_cpu && !name_only && !alias->desc) + aliases[j].name = format_alias_or(buf, + sizeof(buf), + pmu, alias); + aliases[j].name = strdup(aliases[j].name); + if (!aliases[j].name) goto out_enomem; + + aliases[j].desc = long_desc ? alias->long_desc : + alias->desc; + aliases[j].topic = alias->topic; j++; } if (pmu->selectable && @@ -1036,25 +1162,39 @@ void print_pmu_events(const char *event_glob, bool name_only) char *s; if (asprintf(&s, "%s//", pmu->name) < 0) goto out_enomem; - aliases[j] = s; + aliases[j].name = s; j++; } } len = j; - qsort(aliases, len, sizeof(char *), cmp_string); + qsort(aliases, len, sizeof(struct sevent), cmp_sevent); for (j = 0; j < len; j++) { if (name_only) { - printf("%s ", aliases[j]); + printf("%s ", aliases[j].name); continue; } - printf(" %-50s [Kernel PMU event]\n", aliases[j]); + if (aliases[j].desc && !quiet_flag) { + if (numdesc++ == 0) + printf("\n"); + if (aliases[j].topic && (!topic || + strcmp(topic, aliases[j].topic))) { + printf("%s%s:\n", topic ? "\n" : "", + aliases[j].topic); + topic = aliases[j].topic; + } + printf(" %-50s\n", aliases[j].name); + printf("%*s", 8, "["); + wordwrap(aliases[j].desc, 8, columns, 0); + printf("]\n"); + } else + printf(" %-50s [Kernel PMU event]\n", aliases[j].name); printed++; } if (printed && pager_in_use()) printf("\n"); out_free: for (j = 0; j < len; j++) - zfree(&aliases[j]); + zfree(&aliases[j].name); zfree(&aliases); return; diff --git a/tools/perf/util/pmu.h b/tools/perf/util/pmu.h index 743422ad900b..25712034c815 100644 --- a/tools/perf/util/pmu.h +++ b/tools/perf/util/pmu.h @@ -40,6 +40,9 @@ struct perf_pmu_info { struct perf_pmu_alias { char *name; + char *desc; + char *long_desc; + char *topic; struct list_head terms; /* HEAD struct parse_events_term -> list */ struct list_head list; /* ELEM */ char unit[UNIT_MAX_LEN+1]; @@ -71,7 +74,8 @@ int perf_pmu__format_parse(char *dir, struct list_head *head); struct perf_pmu *perf_pmu__scan(struct perf_pmu *pmu); -void print_pmu_events(const char *event_glob, bool name_only); +void print_pmu_events(const char *event_glob, bool name_only, bool quiet, + bool long_desc); 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, diff --git a/tools/perf/util/probe-event.c b/tools/perf/util/probe-event.c index fcfbef07b92d..d281ae2b54e8 100644 --- a/tools/perf/util/probe-event.c +++ b/tools/perf/util/probe-event.c @@ -213,7 +213,7 @@ static int convert_exec_to_group(const char *exec, char **result) goto out; } - for (ptr2 = ptr1; ptr2 != '\0'; ptr2++) { + for (ptr2 = ptr1; *ptr2 != '\0'; ptr2++) { if (!isalnum(*ptr2) && *ptr2 != '_') { *ptr2 = '\0'; break; diff --git a/tools/perf/util/strbuf.h b/tools/perf/util/strbuf.h index b268a6648a5d..318424ea561d 100644 --- a/tools/perf/util/strbuf.h +++ b/tools/perf/util/strbuf.h @@ -66,9 +66,8 @@ static inline ssize_t strbuf_avail(const struct strbuf *sb) { int strbuf_grow(struct strbuf *buf, size_t); static inline int strbuf_setlen(struct strbuf *sb, size_t len) { - int ret; if (!sb->alloc) { - ret = strbuf_grow(sb, 0); + int ret = strbuf_grow(sb, 0); if (ret) return ret; } diff --git a/tools/perf/util/thread.c b/tools/perf/util/thread.c index 8b10a55410a2..f5af87f66663 100644 --- a/tools/perf/util/thread.c +++ b/tools/perf/util/thread.c @@ -14,13 +14,12 @@ int thread__init_map_groups(struct thread *thread, struct machine *machine) { - struct thread *leader; pid_t pid = thread->pid_; if (pid == thread->tid || pid == -1) { thread->mg = map_groups__new(machine); } else { - leader = __machine__findnew_thread(machine, pid, pid); + struct thread *leader = __machine__findnew_thread(machine, pid, pid); if (leader) { thread->mg = map_groups__get(leader->mg); thread__put(leader); @@ -130,11 +129,10 @@ int __thread__set_comm(struct thread *thread, const char *str, u64 timestamp, bool exec) { struct comm *new, *curr = thread__comm(thread); - int err; /* Override the default :tid entry */ if (!thread->comm_set) { - err = comm__override(curr, str, timestamp, exec); + int err = comm__override(curr, str, timestamp, exec); if (err) return err; } else { @@ -270,10 +268,9 @@ static int thread__clone_map_groups(struct thread *thread, int thread__fork(struct thread *thread, struct thread *parent, u64 timestamp) { - int err; - if (parent->comm_set) { const char *comm = thread__comm_str(parent); + int err; if (!comm) return -ENOMEM; err = thread__set_comm(thread, comm, timestamp); |