diff options
Diffstat (limited to 'tools')
32 files changed, 1926 insertions, 66 deletions
diff --git a/tools/build/Build b/tools/build/Build index 63a6c34..76d1a49 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 4d000bc..0248938 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 0d5a0e3..8332959 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 27f3583..190519a 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 be630be..ad22e4e 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 d50f3b58..6518bea 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 8b83714..623f554 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 a126e97..41857cc 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 d710db1..982d643 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 f8ccee1..9aaa6f5 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 146d12a..a74a48d 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 88ee419..ba9322f 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 0000000..9213a12 --- /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 0000000..1408ade --- /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 0000000..79c2133 --- /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 0000000..b0eb274 --- /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 0000000..11d1fa1 --- /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 0000000..d666b10 --- /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 0000000..f67bbb0 --- /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 0000000..278ebd3 --- /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 0000000..2eaef59 --- /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 ea34c5a..d92e020 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 380e84c..8bc2711 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 d306ca1..d30109b 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 18e4519..df85b9e 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 33546c3..4e778ea 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 8d09a97..da246a3 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 2babcdf..b1474dc 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 743422a..2571203 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 fcfbef0..d281ae2 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 b268a66..318424e 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 8b10a55..f5af87f 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); |