diff options
Diffstat (limited to 'tools/perf')
131 files changed, 3221 insertions, 1123 deletions
diff --git a/tools/perf/.gitignore b/tools/perf/.gitignore index 05806ecfc33c..f533e76fb480 100644 --- a/tools/perf/.gitignore +++ b/tools/perf/.gitignore @@ -38,6 +38,7 @@ arch/*/include/generated/ trace/beauty/generated/ pmu-events/pmu-events.c pmu-events/jevents +pmu-events/metric_test.log feature/ libapi/ libbpf/ diff --git a/tools/perf/Documentation/itrace.txt b/tools/perf/Documentation/itrace.txt index 0916bbfe64cb..a97f95825b14 100644 --- a/tools/perf/Documentation/itrace.txt +++ b/tools/perf/Documentation/itrace.txt @@ -1,4 +1,5 @@ i synthesize instructions events + y synthesize cycles events b synthesize branches events (branch misses for Arm SPE) c synthesize branches events (calls only) r synthesize branches events (returns only) @@ -25,7 +26,7 @@ A approximate IPC Z prefer to ignore timestamps (so-called "timeless" decoding) - The default is all events i.e. the same as --itrace=ibxwpe, + The default is all events i.e. the same as --itrace=iybxwpe, except for perf script where it is --itrace=ce In addition, the period (default 100000, except for perf script where it is 1) diff --git a/tools/perf/Documentation/perf-bench.txt b/tools/perf/Documentation/perf-bench.txt index a0529c7fa5ef..f04f0eaded98 100644 --- a/tools/perf/Documentation/perf-bench.txt +++ b/tools/perf/Documentation/perf-bench.txt @@ -18,7 +18,7 @@ COMMON OPTIONS -------------- -r:: --repeat=:: -Specify amount of times to repeat the run (default 10). +Specify number of times to repeat the run (default 10). -f:: --format=:: diff --git a/tools/perf/Documentation/perf-c2c.txt b/tools/perf/Documentation/perf-c2c.txt index 5c5eb2def83e..856f0dfb8e5a 100644 --- a/tools/perf/Documentation/perf-c2c.txt +++ b/tools/perf/Documentation/perf-c2c.txt @@ -22,7 +22,11 @@ you to track down the cacheline contentions. On Intel, the tool is based on load latency and precise store facility events provided by Intel CPUs. On PowerPC, the tool uses random instruction sampling with thresholding feature. On AMD, the tool uses IBS op pmu (due to hardware -limitations, perf c2c is not supported on Zen3 cpus). +limitations, perf c2c is not supported on Zen3 cpus). On Arm64 it uses SPE to +sample load and store operations, therefore hardware and kernel support is +required. See linkperf:perf-arm-spe[1] for a setup guide. Due to the +statistical nature of Arm SPE sampling, not every memory operation will be +sampled. These events provide: - memory address of the access @@ -121,11 +125,17 @@ REPORT OPTIONS perf c2c record --call-graph lbr. Disabled by default. In common cases with call stack overflows, it can recreate better call stacks than the default lbr call stack - output. But this approach is not full proof. There can be cases + output. But this approach is not foolproof. There can be cases where it creates incorrect call stacks from incorrect matches. The known limitations include exception handing such as setjmp/longjmp will have calls/returns not match. +--double-cl:: + Group the detection of shared cacheline events into double cacheline + granularity. Some architectures have an Adjacent Cacheline Prefetch + feature, which causes cacheline sharing to behave like the cacheline + size is doubled. + C2C RECORD ---------- The perf c2c record command setup options related to HITM cacheline analysis @@ -333,4 +343,4 @@ Check Joe's blog on c2c tool for detailed use case explanation: SEE ALSO -------- -linkperf:perf-record[1], linkperf:perf-mem[1] +linkperf:perf-record[1], linkperf:perf-mem[1], linkperf:perf-arm-spe[1] diff --git a/tools/perf/Documentation/perf-intel-pt.txt b/tools/perf/Documentation/perf-intel-pt.txt index 7b6ccd2fa3bf..4c90cc176f81 100644 --- a/tools/perf/Documentation/perf-intel-pt.txt +++ b/tools/perf/Documentation/perf-intel-pt.txt @@ -101,12 +101,12 @@ data is available you can use the 'perf script' tool with all itrace sampling options, which will list all the samples. perf record -e intel_pt//u ls - perf script --itrace=ibxwpe + perf script --itrace=iybxwpe An interesting field that is not printed by default is 'flags' which can be displayed as follows: - perf script --itrace=ibxwpe -F+flags + perf script --itrace=iybxwpe -F+flags The flags are "bcrosyiABExghDt" which stand for branch, call, return, conditional, system, asynchronous, interrupt, transaction abort, trace begin, trace end, @@ -147,16 +147,17 @@ displayed as follows: There are two ways that instructions-per-cycle (IPC) can be calculated depending on the recording. -If the 'cyc' config term (see config terms section below) was used, then IPC is -calculated using the cycle count from CYC packets, otherwise MTC packets are -used - refer to the 'mtc' config term. When MTC is used, however, the values -are less accurate because the timing is less accurate. +If the 'cyc' config term (see config terms section below) was used, then IPC +and cycle events are calculated using the cycle count from CYC packets, otherwise +MTC packets are used - refer to the 'mtc' config term. When MTC is used, however, +the values are less accurate because the timing is less accurate. Because Intel PT does not update the cycle count on every branch or instruction, the values will often be zero. When there are values, they will be the number of instructions and number of cycles since the last update, and thus represent -the average IPC since the last IPC for that event type. Note IPC for "branches" -events is calculated separately from IPC for "instructions" events. +the average IPC cycle count since the last IPC for that event type. +Note IPC for "branches" events is calculated separately from IPC for "instructions" +events. Even with the 'cyc' config term, it is possible to produce IPC information for every change of timestamp, but at the expense of accuracy. That is selected by @@ -900,11 +901,12 @@ Having no option is the same as which, in turn, is the same as - --itrace=cepwx + --itrace=cepwxy The letters are: i synthesize "instructions" events + y synthesize "cycles" events b synthesize "branches" events x synthesize "transactions" events w synthesize "ptwrite" events @@ -927,6 +929,16 @@ The letters are: "Instructions" events look like they were recorded by "perf record -e instructions". +"Cycles" events look like they were recorded by "perf record -e cycles" +(ie., the default). Note that even with CYC packets enabled and no sampling, +these are not fully accurate, since CYC packets are not emitted for each +instruction, only when some other event (like an indirect branch, or a +TNT packet representing multiple branches) happens causes a packet to +be emitted. Thus, it is more effective for attributing cycles to functions +(and possibly basic blocks) than to individual instructions, although it +is not even perfect for functions (although it becomes better if the noretcomp +option is active). + "Branches" events look like they were recorded by "perf record -e branches". "c" and "r" can be combined to get calls and returns. @@ -934,9 +946,9 @@ and "r" can be combined to get calls and returns. 'flags' field can be used in perf script to determine whether the event is a transaction start, commit or abort. -Note that "instructions", "branches" and "transactions" events depend on code -flow packets which can be disabled by using the config term "branch=0". Refer -to the config terms section above. +Note that "instructions", "cycles", "branches" and "transactions" events +depend on code flow packets which can be disabled by using the config term +"branch=0". Refer to the config terms section above. "ptwrite" events record the payload of the ptwrite instruction and whether "fup_on_ptw" was used. "ptwrite" events depend on PTWRITE packets which are @@ -1821,6 +1833,36 @@ Can be compiled and traced: $ +Pipe mode +--------- +Pipe mode is a problem for Intel PT and possibly other auxtrace users. +It's not recommended to use a pipe as data output with Intel PT because +of the following reason. + +Essentially the auxtrace buffers do not behave like the regular perf +event buffers. That is because the head and tail are updated by +software, but in the auxtrace case the data is written by hardware. +So the head and tail do not get updated as data is written. + +In the Intel PT case, the head and tail are updated only when the trace +is disabled by software, for example: + - full-trace, system wide : when buffer passes watermark + - full-trace, not system-wide : when buffer passes watermark or + context switches + - snapshot mode : as above but also when a snapshot is made + - sample mode : as above but also when a sample is made + +That means finished-round ordering doesn't work. An auxtrace buffer +can turn up that has data that extends back in time, possibly to the +very beginning of tracing. + +For a perf.data file, that problem is solved by going through the trace +and queuing up the auxtrace buffers in advance. + +For pipe mode, the order of events and timestamps can presumably +be messed up. + + EXAMPLE ------- diff --git a/tools/perf/Documentation/perf-list.txt b/tools/perf/Documentation/perf-list.txt index c5a3cb0f57c7..d5f78e125efe 100644 --- a/tools/perf/Documentation/perf-list.txt +++ b/tools/perf/Documentation/perf-list.txt @@ -232,7 +232,7 @@ This can be overridden by setting the kernel.perf_event_paranoid sysctl to -1, which allows non root to use these events. For accessing trace point events perf needs to have read access to -/sys/kernel/debug/tracing, even when perf_event_paranoid is in a relaxed +/sys/kernel/tracing, even when perf_event_paranoid is in a relaxed setting. TRACING diff --git a/tools/perf/Documentation/perf-lock.txt b/tools/perf/Documentation/perf-lock.txt index 0f9f720e599d..37aae194a2a1 100644 --- a/tools/perf/Documentation/perf-lock.txt +++ b/tools/perf/Documentation/perf-lock.txt @@ -172,6 +172,11 @@ CONTENTION OPTIONS --lock-addr:: Show lock contention stat by address +-o:: +--lock-owner:: + Show lock contention stat by owners. Implies --threads and + requires --use-bpf. + -Y:: --type-filter=<value>:: Show lock contention only for given lock types (comma separated list). @@ -187,6 +192,12 @@ CONTENTION OPTIONS --lock-filter=<value>:: Show lock contention only for given lock addresses or names (comma separated list). +-S:: +--callstack-filter=<value>:: + Show lock contention only if the callstack contains the given string. + Note that it matches the substring so 'rq' would match both 'raw_spin_rq_lock' + and 'irq_enter_rcu'. + SEE ALSO -------- diff --git a/tools/perf/Documentation/perf-mem.txt b/tools/perf/Documentation/perf-mem.txt index 005c95580b1e..19862572e3f2 100644 --- a/tools/perf/Documentation/perf-mem.txt +++ b/tools/perf/Documentation/perf-mem.txt @@ -23,6 +23,11 @@ Note that on Intel systems the memory latency reported is the use-latency, not the pure load (or store latency). Use latency includes any pipeline queueing delays in addition to the memory subsystem latency. +On Arm64 this uses SPE to sample load and store operations, therefore hardware +and kernel support is required. See linkperf:perf-arm-spe[1] for a setup guide. +Due to the statistical nature of SPE sampling, not every memory operation will +be sampled. + OPTIONS ------- <command>...:: @@ -93,4 +98,4 @@ all perf record options. SEE ALSO -------- -linkperf:perf-record[1], linkperf:perf-report[1] +linkperf:perf-record[1], linkperf:perf-report[1], linkperf:perf-arm-spe[1] diff --git a/tools/perf/Documentation/perf-probe.txt b/tools/perf/Documentation/perf-probe.txt index 7f8e8ba3a787..5c43a6edc0e5 100644 --- a/tools/perf/Documentation/perf-probe.txt +++ b/tools/perf/Documentation/perf-probe.txt @@ -222,7 +222,7 @@ probe syntax, 'SRC' means the source file path, 'ALN' is start line number, and 'ALN2' is end line number in the file. It is also possible to specify how many lines to show by using 'NUM'. Moreover, 'FUNC@SRC' combination is good for searching a specific function when several functions share same name. -So, "source.c:100-120" shows lines between 100th to l20th in source.c file. And "func:10+20" shows 20 lines from 10th line of func function. +So, "source.c:100-120" shows lines between 100th to 120th in source.c file. And "func:10+20" shows 20 lines from 10th line of func function. LAZY MATCHING ------------- diff --git a/tools/perf/Documentation/perf-report.txt b/tools/perf/Documentation/perf-report.txt index 4fa509b15948..c242e8da6b1a 100644 --- a/tools/perf/Documentation/perf-report.txt +++ b/tools/perf/Documentation/perf-report.txt @@ -115,6 +115,8 @@ OPTIONS - p_stage_cyc: On powerpc, this presents the number of cycles spent in a pipeline stage. And currently supported only on powerpc. - addr: (Full) virtual address of the sampled instruction + - retire_lat: On X86, this reports pipeline stall of this instruction compared + to the previous instruction in cycles. And currently supported only on X86 By default, comm, dso and symbol keys are used. (i.e. --sort comm,dso,symbol) @@ -507,7 +509,7 @@ include::itrace.txt[] perf record --call-graph lbr. Disabled by default. In common cases with call stack overflows, it can recreate better call stacks than the default lbr call stack - output. But this approach is not full proof. There can be cases + output. But this approach is not foolproof. There can be cases where it creates incorrect call stacks from incorrect matches. The known limitations include exception handing such as setjmp/longjmp will have calls/returns not match. diff --git a/tools/perf/Documentation/perf-script-perl.txt b/tools/perf/Documentation/perf-script-perl.txt index fa4f39d305a7..5b479f5e62ff 100644 --- a/tools/perf/Documentation/perf-script-perl.txt +++ b/tools/perf/Documentation/perf-script-perl.txt @@ -55,7 +55,7 @@ Traces meant to be processed using a script should be recorded with the above option: -a to enable system-wide collection. The format file for the sched_wakeup event defines the following fields -(see /sys/kernel/debug/tracing/events/sched/sched_wakeup/format): +(see /sys/kernel/tracing/events/sched/sched_wakeup/format): ---- format: diff --git a/tools/perf/Documentation/perf-script-python.txt b/tools/perf/Documentation/perf-script-python.txt index cf4b7f4b625a..6a8581012e16 100644 --- a/tools/perf/Documentation/perf-script-python.txt +++ b/tools/perf/Documentation/perf-script-python.txt @@ -319,7 +319,7 @@ So those are the essential steps in writing and running a script. The process can be generalized to any tracepoint or set of tracepoints you're interested in - basically find the tracepoint(s) you're interested in by looking at the list of available events shown by -'perf list' and/or look in /sys/kernel/debug/tracing/events/ for +'perf list' and/or look in /sys/kernel/tracing/events/ for detailed event and field info, record the corresponding trace data using 'perf record', passing it the list of interesting events, generate a skeleton script using 'perf script -g python' and modify the @@ -449,7 +449,7 @@ Traces meant to be processed using a script should be recorded with the above option: -a to enable system-wide collection. The format file for the sched_wakeup event defines the following fields -(see /sys/kernel/debug/tracing/events/sched/sched_wakeup/format): +(see /sys/kernel/tracing/events/sched/sched_wakeup/format): ---- format: diff --git a/tools/perf/Documentation/perf-script.txt b/tools/perf/Documentation/perf-script.txt index 68e37de5fae4..777a0d8ba7d1 100644 --- a/tools/perf/Documentation/perf-script.txt +++ b/tools/perf/Documentation/perf-script.txt @@ -134,7 +134,7 @@ OPTIONS srcline, period, iregs, uregs, brstack, brstacksym, flags, bpf-output, brstackinsn, brstackinsnlen, brstackoff, callindent, insn, insnlen, synth, phys_addr, metric, misc, srccode, ipc, data_page_size, code_page_size, ins_lat, - machine_pid, vcpu. + machine_pid, vcpu, cgroup, retire_lat. Field list can be prepended with the type, trace, sw or hw, to indicate to which event type the field list applies. e.g., -F sw:comm,tid,time,ip,sym and -F trace:time,cpu,trace @@ -231,6 +231,9 @@ OPTIONS perf inject to insert a perf.data file recorded inside a virtual machine into a perf.data file recorded on the host at the same time. + The cgroup fields requires sample having the cgroup id which is saved + when "--all-cgroups" option is passed to 'perf record'. + Finally, a user may not set fields to none for all event types. i.e., -F "" is not allowed. @@ -502,7 +505,7 @@ include::itrace.txt[] perf record --call-graph lbr. Disabled by default. In common cases with call stack overflows, it can recreate better call stacks than the default lbr call stack - output. But this approach is not full proof. There can be cases + output. But this approach is not foolproof. There can be cases where it creates incorrect call stacks from incorrect matches. The known limitations include exception handing such as setjmp/longjmp will have calls/returns not match. diff --git a/tools/perf/Documentation/perf-test.txt b/tools/perf/Documentation/perf-test.txt index b329c65d7f40..951a2f262872 100644 --- a/tools/perf/Documentation/perf-test.txt +++ b/tools/perf/Documentation/perf-test.txt @@ -34,3 +34,6 @@ OPTIONS -F:: --dont-fork:: Do not fork child for each test, run all tests within single process. + +--dso:: + Specify a DSO for the "Symbols" test. diff --git a/tools/perf/Documentation/perf-top.txt b/tools/perf/Documentation/perf-top.txt index e534d709cc5a..c60e615b7183 100644 --- a/tools/perf/Documentation/perf-top.txt +++ b/tools/perf/Documentation/perf-top.txt @@ -334,7 +334,7 @@ use '-e e1 -e e2 -G foo,foo' or just use '-e e1 -e e2 -G foo'. callgraph. The option must be used with --call-graph lbr recording. Disabled by default. In common cases with call stack overflows, it can recreate better call stacks than the default lbr call stack - output. But this approach is not full proof. There can be cases + output. But this approach is not foolproof. There can be cases where it creates incorrect call stacks from incorrect matches. The known limitations include exception handing such as setjmp/longjmp will have calls/returns not match. diff --git a/tools/perf/Makefile.config b/tools/perf/Makefile.config index 5b8784675903..3519a0139026 100644 --- a/tools/perf/Makefile.config +++ b/tools/perf/Makefile.config @@ -1208,10 +1208,6 @@ ifneq ($(NO_LIBTRACEEVENT),1) LIBTRACEEVENT_VERSION_CPP := $(shell expr $(LIBTRACEEVENT_VERSION_1) \* 255 \* 255 + $(LIBTRACEEVENT_VERSION_2) \* 255 + $(LIBTRACEEVENT_VERSION_3)) CFLAGS += -DLIBTRACEEVENT_VERSION=$(LIBTRACEEVENT_VERSION_CPP) $(call detected,CONFIG_LIBTRACEEVENT) - LIBTRACEEVENT_VERSION_WITH_TEP_FIELD_IS_RELATIVE := $(shell expr 1 \* 255 \* 255 + 5 \* 255 + 0) # 1.5.0 - ifeq ($(shell test $(LIBTRACEEVENT_VERSION_CPP) -gt $(LIBTRACEEVENT_VERSION_WITH_TEP_FIELD_IS_RELATIVE); echo $$?),0) - CFLAGS += -DHAVE_LIBTRACEEVENT_TEP_FIELD_IS_RELATIVE - endif else dummy := $(warning Warning: libtraceevent is missing limiting functionality, please install libtraceevent-dev/libtraceevent-devel) endif diff --git a/tools/perf/Makefile.perf b/tools/perf/Makefile.perf index b7d9c4206230..bac9272682b7 100644 --- a/tools/perf/Makefile.perf +++ b/tools/perf/Makefile.perf @@ -1103,6 +1103,7 @@ clean:: $(LIBAPI)-clean $(LIBBPF)-clean $(LIBSUBCMD)-clean $(LIBSYMBOL)-clean $( $(OUTPUT)util/intel-pt-decoder/inat-tables.c \ $(OUTPUT)tests/llvm-src-{base,kbuild,prologue,relocation}.c \ $(OUTPUT)pmu-events/pmu-events.c \ + $(OUTPUT)pmu-events/metric_test.log \ $(OUTPUT)$(fadvise_advice_array) \ $(OUTPUT)$(fsconfig_arrays) \ $(OUTPUT)$(fsmount_arrays) \ diff --git a/tools/perf/arch/arm/util/auxtrace.c b/tools/perf/arch/arm/util/auxtrace.c index deeb163999ce..adec6c9ee11d 100644 --- a/tools/perf/arch/arm/util/auxtrace.c +++ b/tools/perf/arch/arm/util/auxtrace.c @@ -55,17 +55,16 @@ static struct perf_pmu **find_all_arm_spe_pmus(int *nr_spes, int *err) static struct perf_pmu **find_all_hisi_ptt_pmus(int *nr_ptts, int *err) { - const char *sysfs = sysfs__mountpoint(); struct perf_pmu **hisi_ptt_pmus = NULL; struct dirent *dent; char path[PATH_MAX]; DIR *dir = NULL; int idx = 0; - snprintf(path, PATH_MAX, "%s" EVENT_SOURCE_DEVICE_PATH, sysfs); + perf_pmu__event_source_devices_scnprintf(path, sizeof(path)); dir = opendir(path); if (!dir) { - pr_err("can't read directory '%s'\n", EVENT_SOURCE_DEVICE_PATH); + pr_err("can't read directory '%s'\n", path); *err = -EINVAL; return NULL; } diff --git a/tools/perf/arch/arm/util/cs-etm.c b/tools/perf/arch/arm/util/cs-etm.c index a346d5f3dafa..7f71c8a237ff 100644 --- a/tools/perf/arch/arm/util/cs-etm.c +++ b/tools/perf/arch/arm/util/cs-etm.c @@ -53,7 +53,17 @@ static const char * const metadata_etmv4_ro[] = { [CS_ETMV4_TRCIDR2] = "trcidr/trcidr2", [CS_ETMV4_TRCIDR8] = "trcidr/trcidr8", [CS_ETMV4_TRCAUTHSTATUS] = "mgmt/trcauthstatus", - [CS_ETE_TRCDEVARCH] = "mgmt/trcdevarch" + [CS_ETMV4_TS_SOURCE] = "ts_source", +}; + +static const char * const metadata_ete_ro[] = { + [CS_ETE_TRCIDR0] = "trcidr/trcidr0", + [CS_ETE_TRCIDR1] = "trcidr/trcidr1", + [CS_ETE_TRCIDR2] = "trcidr/trcidr2", + [CS_ETE_TRCIDR8] = "trcidr/trcidr8", + [CS_ETE_TRCAUTHSTATUS] = "mgmt/trcauthstatus", + [CS_ETE_TRCDEVARCH] = "mgmt/trcdevarch", + [CS_ETE_TS_SOURCE] = "ts_source", }; static bool cs_etm_is_etmv4(struct auxtrace_record *itr, int cpu); @@ -273,9 +283,15 @@ static int cs_etm_set_sink_attr(struct perf_pmu *pmu, ret = perf_pmu__scan_file(pmu, path, "%x", &hash); if (ret != 1) { - pr_err("failed to set sink \"%s\" on event %s with %d (%s)\n", - sink, evsel__name(evsel), errno, - str_error_r(errno, msg, sizeof(msg))); + if (errno == ENOENT) + pr_err("Couldn't find sink \"%s\" on event %s\n" + "Missing kernel or device support?\n\n" + "Hint: An appropriate sink will be picked automatically if one isn't specified.\n", + sink, evsel__name(evsel)); + else + pr_err("Failed to set sink \"%s\" on event %s with %d (%s)\n", + sink, evsel__name(evsel), errno, + str_error_r(errno, msg, sizeof(msg))); return ret; } @@ -605,6 +621,32 @@ static int cs_etm_get_ro(struct perf_pmu *pmu, int cpu, const char *path) return val; } +static int cs_etm_get_ro_signed(struct perf_pmu *pmu, int cpu, const char *path) +{ + char pmu_path[PATH_MAX]; + int scan; + int val = 0; + + /* Get RO metadata from sysfs */ + snprintf(pmu_path, PATH_MAX, "cpu%d/%s", cpu, path); + + scan = perf_pmu__scan_file(pmu, pmu_path, "%d", &val); + if (scan != 1) + pr_err("%s: error reading: %s\n", __func__, pmu_path); + + return val; +} + +static bool cs_etm_pmu_path_exists(struct perf_pmu *pmu, int cpu, const char *path) +{ + char pmu_path[PATH_MAX]; + + /* Get RO metadata from sysfs */ + snprintf(pmu_path, PATH_MAX, "cpu%d/%s", cpu, path); + + return perf_pmu__file_exists(pmu, pmu_path); +} + #define TRCDEVARCH_ARCHPART_SHIFT 0 #define TRCDEVARCH_ARCHPART_MASK GENMASK(11, 0) #define TRCDEVARCH_ARCHPART(x) (((x) & TRCDEVARCH_ARCHPART_MASK) >> TRCDEVARCH_ARCHPART_SHIFT) @@ -617,7 +659,7 @@ static bool cs_etm_is_ete(struct auxtrace_record *itr, int cpu) { struct cs_etm_recording *ptr = container_of(itr, struct cs_etm_recording, itr); struct perf_pmu *cs_etm_pmu = ptr->cs_etm_pmu; - int trcdevarch = cs_etm_get_ro(cs_etm_pmu, cpu, metadata_etmv4_ro[CS_ETE_TRCDEVARCH]); + int trcdevarch = cs_etm_get_ro(cs_etm_pmu, cpu, metadata_ete_ro[CS_ETE_TRCDEVARCH]); /* * ETE if ARCHVER is 5 (ARCHVER is 4 for ETM) and ARCHPART is 0xA13. @@ -646,6 +688,51 @@ static void cs_etm_save_etmv4_header(__u64 data[], struct auxtrace_record *itr, metadata_etmv4_ro[CS_ETMV4_TRCIDR8]); data[CS_ETMV4_TRCAUTHSTATUS] = cs_etm_get_ro(cs_etm_pmu, cpu, metadata_etmv4_ro[CS_ETMV4_TRCAUTHSTATUS]); + + /* Kernels older than 5.19 may not expose ts_source */ + if (cs_etm_pmu_path_exists(cs_etm_pmu, cpu, metadata_etmv4_ro[CS_ETMV4_TS_SOURCE])) + data[CS_ETMV4_TS_SOURCE] = (__u64) cs_etm_get_ro_signed(cs_etm_pmu, cpu, + metadata_etmv4_ro[CS_ETMV4_TS_SOURCE]); + else { + pr_warning("[%03d] pmu file 'ts_source' not found. Fallback to safe value (-1)\n", + cpu); + data[CS_ETMV4_TS_SOURCE] = (__u64) -1; + } +} + +static void cs_etm_save_ete_header(__u64 data[], struct auxtrace_record *itr, int cpu) +{ + struct cs_etm_recording *ptr = container_of(itr, struct cs_etm_recording, itr); + struct perf_pmu *cs_etm_pmu = ptr->cs_etm_pmu; + + /* Get trace configuration register */ + data[CS_ETE_TRCCONFIGR] = cs_etmv4_get_config(itr); + /* Get traceID from the framework */ + data[CS_ETE_TRCTRACEIDR] = coresight_get_trace_id(cpu); + /* Get read-only information from sysFS */ + data[CS_ETE_TRCIDR0] = cs_etm_get_ro(cs_etm_pmu, cpu, + metadata_ete_ro[CS_ETE_TRCIDR0]); + data[CS_ETE_TRCIDR1] = cs_etm_get_ro(cs_etm_pmu, cpu, + metadata_ete_ro[CS_ETE_TRCIDR1]); + data[CS_ETE_TRCIDR2] = cs_etm_get_ro(cs_etm_pmu, cpu, + metadata_ete_ro[CS_ETE_TRCIDR2]); + data[CS_ETE_TRCIDR8] = cs_etm_get_ro(cs_etm_pmu, cpu, + metadata_ete_ro[CS_ETE_TRCIDR8]); + data[CS_ETE_TRCAUTHSTATUS] = cs_etm_get_ro(cs_etm_pmu, cpu, + metadata_ete_ro[CS_ETE_TRCAUTHSTATUS]); + /* ETE uses the same registers as ETMv4 plus TRCDEVARCH */ + data[CS_ETE_TRCDEVARCH] = cs_etm_get_ro(cs_etm_pmu, cpu, + metadata_ete_ro[CS_ETE_TRCDEVARCH]); + + /* Kernels older than 5.19 may not expose ts_source */ + if (cs_etm_pmu_path_exists(cs_etm_pmu, cpu, metadata_ete_ro[CS_ETE_TS_SOURCE])) + data[CS_ETE_TS_SOURCE] = (__u64) cs_etm_get_ro_signed(cs_etm_pmu, cpu, + metadata_ete_ro[CS_ETE_TS_SOURCE]); + else { + pr_warning("[%03d] pmu file 'ts_source' not found. Fallback to safe value (-1)\n", + cpu); + data[CS_ETE_TS_SOURCE] = (__u64) -1; + } } static void cs_etm_get_metadata(int cpu, u32 *offset, @@ -661,11 +748,7 @@ static void cs_etm_get_metadata(int cpu, u32 *offset, /* first see what kind of tracer this cpu is affined to */ if (cs_etm_is_ete(itr, cpu)) { magic = __perf_cs_ete_magic; - /* ETE uses the same registers as ETMv4 plus TRCDEVARCH */ - cs_etm_save_etmv4_header(&info->priv[*offset], itr, cpu); - info->priv[*offset + CS_ETE_TRCDEVARCH] = - cs_etm_get_ro(cs_etm_pmu, cpu, - metadata_etmv4_ro[CS_ETE_TRCDEVARCH]); + cs_etm_save_ete_header(&info->priv[*offset], itr, cpu); /* How much space was used */ increment = CS_ETE_PRIV_MAX; diff --git a/tools/perf/arch/arm64/util/pmu.c b/tools/perf/arch/arm64/util/pmu.c index 477e513972a4..fa143acb4c8d 100644 --- a/tools/perf/arch/arm64/util/pmu.c +++ b/tools/perf/arch/arm64/util/pmu.c @@ -3,8 +3,10 @@ #include <internal/cpumap.h> #include "../../../util/cpumap.h" #include "../../../util/pmu.h" +#include <api/fs/fs.h> +#include <math.h> -const struct pmu_events_table *pmu_events_table__find(void) +static struct perf_pmu *pmu__find_core_pmu(void) { struct perf_pmu *pmu = NULL; @@ -19,8 +21,46 @@ const struct pmu_events_table *pmu_events_table__find(void) if (pmu->cpus->nr != cpu__max_cpu().cpu) return NULL; - return perf_pmu__find_table(pmu); + return pmu; } + return NULL; +} + +const struct pmu_metrics_table *pmu_metrics_table__find(void) +{ + struct perf_pmu *pmu = pmu__find_core_pmu(); + + if (pmu) + return perf_pmu__find_metrics_table(pmu); return NULL; } + +const struct pmu_events_table *pmu_events_table__find(void) +{ + struct perf_pmu *pmu = pmu__find_core_pmu(); + + if (pmu) + return perf_pmu__find_events_table(pmu); + + return NULL; +} + +double perf_pmu__cpu_slots_per_cycle(void) +{ + char path[PATH_MAX]; + unsigned long long slots = 0; + struct perf_pmu *pmu = pmu__find_core_pmu(); + + if (pmu) { + perf_pmu__pathname_scnprintf(path, sizeof(path), + pmu->name, "caps/slots"); + /* + * The value of slots is not greater than 32 bits, but sysfs__read_int + * can't read value with 0x prefix, so use sysfs__read_ull instead. + */ + sysfs__read_ull(path, &slots); + } + + return slots ? (double)slots : NAN; +} diff --git a/tools/perf/arch/powerpc/util/header.c b/tools/perf/arch/powerpc/util/header.c index e8fe36b10d20..78eef77d8a8d 100644 --- a/tools/perf/arch/powerpc/util/header.c +++ b/tools/perf/arch/powerpc/util/header.c @@ -40,11 +40,11 @@ get_cpuid_str(struct perf_pmu *pmu __maybe_unused) return bufp; } -int arch_get_runtimeparam(const struct pmu_event *pe) +int arch_get_runtimeparam(const struct pmu_metric *pm) { int count; char path[PATH_MAX] = "/devices/hv_24x7/interface/"; - atoi(pe->aggr_mode) == PerChip ? strcat(path, "sockets") : strcat(path, "coresperchip"); + atoi(pm->aggr_mode) == PerChip ? strcat(path, "sockets") : strcat(path, "coresperchip"); return sysfs__read_int(path, &count) < 0 ? 1 : count; } diff --git a/tools/perf/arch/x86/tests/sample-parsing.c b/tools/perf/arch/x86/tests/sample-parsing.c index 690c7c07e90d..a061e8619267 100644 --- a/tools/perf/arch/x86/tests/sample-parsing.c +++ b/tools/perf/arch/x86/tests/sample-parsing.c @@ -27,8 +27,10 @@ static bool samples_same(const struct perf_sample *s1, const struct perf_sample *s2, u64 type) { - if (type & PERF_SAMPLE_WEIGHT_STRUCT) + if (type & PERF_SAMPLE_WEIGHT_STRUCT) { COMP(ins_lat); + COMP(retire_lat); + } return true; } @@ -48,6 +50,7 @@ static int do_test(u64 sample_type) struct perf_sample sample = { .weight = 101, .ins_lat = 102, + .retire_lat = 103, }; struct perf_sample sample_out; size_t i, sz, bufsz; diff --git a/tools/perf/arch/x86/util/event.c b/tools/perf/arch/x86/util/event.c index a3acefe6d0c6..e4288d09f3a0 100644 --- a/tools/perf/arch/x86/util/event.c +++ b/tools/perf/arch/x86/util/event.c @@ -89,6 +89,7 @@ void arch_perf_parse_sample_weight(struct perf_sample *data, else { data->weight = weight.var1_dw; data->ins_lat = weight.var2_w; + data->retire_lat = weight.var3_w; } } @@ -100,5 +101,25 @@ void arch_perf_synthesize_sample_weight(const struct perf_sample *data, if (type & PERF_SAMPLE_WEIGHT_STRUCT) { *array &= 0xffffffff; *array |= ((u64)data->ins_lat << 32); + *array |= ((u64)data->retire_lat << 48); } } + +const char *arch_perf_header_entry(const char *se_header) +{ + if (!strcmp(se_header, "Local Pipeline Stage Cycle")) + return "Local Retire Latency"; + else if (!strcmp(se_header, "Pipeline Stage Cycle")) + return "Retire Latency"; + + return se_header; +} + +int arch_support_sort_key(const char *sort_key) +{ + if (!strcmp(sort_key, "p_stage_cyc")) + return 1; + if (!strcmp(sort_key, "local_p_stage_cyc")) + return 1; + return 0; +} diff --git a/tools/perf/arch/x86/util/pmu.c b/tools/perf/arch/x86/util/pmu.c index 74d69db1ea99..358340b34243 100644 --- a/tools/perf/arch/x86/util/pmu.c +++ b/tools/perf/arch/x86/util/pmu.c @@ -15,8 +15,6 @@ #include "../../../util/pmu.h" #include "../../../util/fncache.h" -#define TEMPLATE_ALIAS "%s/bus/event_source/devices/%s/alias" - struct pmu_alias { char *name; char *alias; @@ -72,18 +70,14 @@ static int setup_pmu_alias_list(void) char path[PATH_MAX]; DIR *dir; struct dirent *dent; - const char *sysfs = sysfs__mountpoint(); struct pmu_alias *pmu_alias; char buf[MAX_PMU_NAME_LEN]; FILE *file; int ret = -ENOMEM; - if (!sysfs) + if (!perf_pmu__event_source_devices_scnprintf(path, sizeof(path))) return -1; - snprintf(path, PATH_MAX, - "%s" EVENT_SOURCE_DEVICE_PATH, sysfs); - dir = opendir(path); if (!dir) return -errno; @@ -93,9 +87,7 @@ static int setup_pmu_alias_list(void) !strcmp(dent->d_name, "..")) continue; - snprintf(path, PATH_MAX, - TEMPLATE_ALIAS, sysfs, dent->d_name); - + perf_pmu__pathname_scnprintf(path, sizeof(path), dent->d_name, "alias"); if (!file_available(path)) continue; diff --git a/tools/perf/bench/bench.h b/tools/perf/bench/bench.h index a5d49b3b6a09..e43893151a3e 100644 --- a/tools/perf/bench/bench.h +++ b/tools/perf/bench/bench.h @@ -22,6 +22,8 @@ int bench_numa(int argc, const char **argv); int bench_sched_messaging(int argc, const char **argv); int bench_sched_pipe(int argc, const char **argv); int bench_syscall_basic(int argc, const char **argv); +int bench_syscall_getpgid(int argc, const char **argv); +int bench_syscall_execve(int argc, const char **argv); int bench_mem_memcpy(int argc, const char **argv); int bench_mem_memset(int argc, const char **argv); int bench_mem_find_bit(int argc, const char **argv); diff --git a/tools/perf/bench/syscall.c b/tools/perf/bench/syscall.c index 9b751016f4b6..fe79f7f3091e 100644 --- a/tools/perf/bench/syscall.c +++ b/tools/perf/bench/syscall.c @@ -14,6 +14,7 @@ #include <sys/time.h> #include <sys/syscall.h> #include <sys/types.h> +#include <sys/wait.h> #include <unistd.h> #include <stdlib.h> @@ -30,25 +31,77 @@ static const char * const bench_syscall_usage[] = { NULL }; -int bench_syscall_basic(int argc, const char **argv) +static void test_execve(void) +{ + const char *pathname = "/bin/true"; + char *const argv[] = { (char *)pathname, NULL }; + pid_t pid = fork(); + + if (pid < 0) { + fprintf(stderr, "fork failed\n"); + exit(1); + } else if (pid == 0) { + execve(pathname, argv, NULL); + fprintf(stderr, "execve /bin/true failed\n"); + exit(1); + } else { + if (waitpid(pid, NULL, 0) < 0) { + fprintf(stderr, "waitpid failed\n"); + exit(1); + } + } +} + +static int bench_syscall_common(int argc, const char **argv, int syscall) { struct timeval start, stop, diff; unsigned long long result_usec = 0; + const char *name = NULL; int i; argc = parse_options(argc, argv, options, bench_syscall_usage, 0); gettimeofday(&start, NULL); - for (i = 0; i < loops; i++) - getppid(); + for (i = 0; i < loops; i++) { + switch (syscall) { + case __NR_getppid: + getppid(); + break; + case __NR_getpgid: + getpgid(0); + break; + case __NR_execve: + test_execve(); + /* Only loop 10000 times to save time */ + if (i == 10000) + loops = 10000; + break; + default: + break; + } + } gettimeofday(&stop, NULL); timersub(&stop, &start, &diff); + switch (syscall) { + case __NR_getppid: + name = "getppid()"; + break; + case __NR_getpgid: + name = "getpgid()"; + break; + case __NR_execve: + name = "execve()"; + break; + default: + break; + } + switch (bench_format) { case BENCH_FORMAT_DEFAULT: - printf("# Executed %'d getppid() calls\n", loops); + printf("# Executed %'d %s calls\n", loops, name); result_usec = diff.tv_sec * 1000000; result_usec += diff.tv_usec; @@ -79,3 +132,18 @@ int bench_syscall_basic(int argc, const char **argv) return 0; } + +int bench_syscall_basic(int argc, const char **argv) +{ + return bench_syscall_common(argc, argv, __NR_getppid); +} + +int bench_syscall_getpgid(int argc, const char **argv) +{ + return bench_syscall_common(argc, argv, __NR_getpgid); +} + +int bench_syscall_execve(int argc, const char **argv) +{ + return bench_syscall_common(argc, argv, __NR_execve); +} diff --git a/tools/perf/builtin-bench.c b/tools/perf/builtin-bench.c index 334ab897aae3..814e9afc86f6 100644 --- a/tools/perf/builtin-bench.c +++ b/tools/perf/builtin-bench.c @@ -52,6 +52,8 @@ static struct bench sched_benchmarks[] = { static struct bench syscall_benchmarks[] = { { "basic", "Benchmark for basic getppid(2) calls", bench_syscall_basic }, + { "getpgid", "Benchmark for getpgid(2) calls", bench_syscall_getpgid }, + { "execve", "Benchmark for execve(2) calls", bench_syscall_execve }, { "all", "Run all syscall benchmarks", NULL }, { NULL, NULL, NULL }, }; @@ -150,7 +152,7 @@ unsigned int bench_repeat = 10; /* default number of times to repeat the run */ static const struct option bench_options[] = { OPT_STRING('f', "format", &bench_format_str, "default|simple", "Specify the output formatting style"), - OPT_UINTEGER('r', "repeat", &bench_repeat, "Specify amount of times to repeat the run"), + OPT_UINTEGER('r', "repeat", &bench_repeat, "Specify number of times to repeat the run"), OPT_END() }; diff --git a/tools/perf/builtin-c2c.c b/tools/perf/builtin-c2c.c index 52d94c7dd836..56974eae0638 100644 --- a/tools/perf/builtin-c2c.c +++ b/tools/perf/builtin-c2c.c @@ -524,7 +524,7 @@ static int dcacheline_entry(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, char buf[20]; if (he->mem_info) - addr = cl_address(he->mem_info->daddr.addr); + addr = cl_address(he->mem_info->daddr.addr, chk_double_cl); return scnprintf(hpp->buf, hpp->size, "%*s", width, HEX_STR(buf, addr)); } @@ -562,7 +562,7 @@ static int offset_entry(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, char buf[20]; if (he->mem_info) - addr = cl_offset(he->mem_info->daddr.al_addr); + addr = cl_offset(he->mem_info->daddr.al_addr, chk_double_cl); return scnprintf(hpp->buf, hpp->size, "%*s", width, HEX_STR(buf, addr)); } @@ -574,9 +574,10 @@ offset_cmp(struct perf_hpp_fmt *fmt __maybe_unused, uint64_t l = 0, r = 0; if (left->mem_info) - l = cl_offset(left->mem_info->daddr.addr); + l = cl_offset(left->mem_info->daddr.addr, chk_double_cl); + if (right->mem_info) - r = cl_offset(right->mem_info->daddr.addr); + r = cl_offset(right->mem_info->daddr.addr, chk_double_cl); return (int64_t)(r - l); } @@ -2590,7 +2591,7 @@ perf_c2c_cacheline_browser__title(struct hist_browser *browser, he = cl_browser->he; if (he->mem_info) - addr = cl_address(he->mem_info->daddr.addr); + addr = cl_address(he->mem_info->daddr.addr, chk_double_cl); scnprintf(bf, size, "Cacheline 0x%lx", addr); return 0; @@ -2788,15 +2789,16 @@ static int ui_quirks(void) if (!c2c.use_stdio) { dim_offset.width = 5; dim_offset.header = header_offset_tui; - nodestr = "CL"; + nodestr = chk_double_cl ? "Double-CL" : "CL"; } dim_percent_costly_snoop.header = percent_costly_snoop_header[c2c.display]; /* Fix the zero line for dcacheline column. */ - buf = fill_line("Cacheline", dim_dcacheline.width + - dim_dcacheline_node.width + - dim_dcacheline_count.width + 4); + buf = fill_line(chk_double_cl ? "Double-Cacheline" : "Cacheline", + dim_dcacheline.width + + dim_dcacheline_node.width + + dim_dcacheline_count.width + 4); if (!buf) return -ENOMEM; @@ -3037,6 +3039,7 @@ static int perf_c2c__report(int argc, const char **argv) OPT_BOOLEAN('f', "force", &symbol_conf.force, "don't complain, do it"), OPT_BOOLEAN(0, "stitch-lbr", &c2c.stitch_lbr, "Enable LBR callgraph stitching approach"), + OPT_BOOLEAN(0, "double-cl", &chk_double_cl, "Detect adjacent cacheline false sharing"), OPT_PARENT(c2c_options), OPT_END() }; diff --git a/tools/perf/builtin-inject.c b/tools/perf/builtin-inject.c index 3f4e4dd5abf3..10bb1d494258 100644 --- a/tools/perf/builtin-inject.c +++ b/tools/perf/builtin-inject.c @@ -215,14 +215,14 @@ static int perf_event__repipe_event_update(struct perf_tool *tool, #ifdef HAVE_AUXTRACE_SUPPORT -static int copy_bytes(struct perf_inject *inject, int fd, off_t size) +static int copy_bytes(struct perf_inject *inject, struct perf_data *data, off_t size) { char buf[4096]; ssize_t ssz; int ret; while (size > 0) { - ssz = read(fd, buf, min(size, (off_t)sizeof(buf))); + ssz = perf_data__read(data, buf, min(size, (off_t)sizeof(buf))); if (ssz < 0) return -errno; ret = output_bytes(inject, buf, ssz); @@ -260,7 +260,7 @@ static s64 perf_event__repipe_auxtrace(struct perf_session *session, ret = output_bytes(inject, event, event->header.size); if (ret < 0) return ret; - ret = copy_bytes(inject, perf_data__fd(session->data), + ret = copy_bytes(inject, session->data, event->auxtrace.size); } else { ret = output_bytes(inject, event, @@ -538,6 +538,7 @@ static int perf_event__repipe_buildid_mmap2(struct perf_tool *tool, dso->hit = 1; } dso__put(dso); + perf_event__repipe(tool, event, sample, machine); return 0; } diff --git a/tools/perf/builtin-kmem.c b/tools/perf/builtin-kmem.c index 8ae0a1535293..f3029742b800 100644 --- a/tools/perf/builtin-kmem.c +++ b/tools/perf/builtin-kmem.c @@ -653,7 +653,6 @@ static const struct { { "__GFP_HIGHMEM", "HM" }, { "GFP_DMA32", "D32" }, { "__GFP_HIGH", "H" }, - { "__GFP_ATOMIC", "_A" }, { "__GFP_IO", "I" }, { "__GFP_FS", "F" }, { "__GFP_NOWARN", "NWR" }, diff --git a/tools/perf/builtin-list.c b/tools/perf/builtin-list.c index 137d73edb541..791f513ae5b4 100644 --- a/tools/perf/builtin-list.c +++ b/tools/perf/builtin-list.c @@ -99,8 +99,7 @@ static void default_print_event(void *ps, const char *pmu_name, const char *topi const char *scale_unit __maybe_unused, bool deprecated, const char *event_type_desc, const char *desc, const char *long_desc, - const char *encoding_desc, - const char *metric_name, const char *metric_expr) + const char *encoding_desc) { struct print_state *print_state = ps; int pos; @@ -159,10 +158,6 @@ static void default_print_event(void *ps, const char *pmu_name, const char *topi if (print_state->detailed && encoding_desc) { printf("%*s", 8, ""); wordwrap(encoding_desc, 8, pager_get_columns(), 0); - if (metric_name) - printf(" MetricName: %s", metric_name); - if (metric_expr) - printf(" MetricExpr: %s", metric_expr); putchar('\n'); } } @@ -308,8 +303,7 @@ static void json_print_event(void *ps, const char *pmu_name, const char *topic, const char *scale_unit, bool deprecated, const char *event_type_desc, const char *desc, const char *long_desc, - const char *encoding_desc, - const char *metric_name, const char *metric_expr) + const char *encoding_desc) { struct json_print_state *print_state = ps; bool need_sep = false; @@ -366,16 +360,6 @@ static void json_print_event(void *ps, const char *pmu_name, const char *topic, encoding_desc); need_sep = true; } - if (metric_name) { - fix_escape_printf(&buf, "%s\t\"MetricName\": \"%S\"", need_sep ? ",\n" : "", - metric_name); - need_sep = true; - } - if (metric_expr) { - fix_escape_printf(&buf, "%s\t\"MetricExpr\": \"%S\"", need_sep ? ",\n" : "", - metric_expr); - need_sep = true; - } printf("%s}", need_sep ? "\n" : ""); strbuf_release(&buf); } diff --git a/tools/perf/builtin-lock.c b/tools/perf/builtin-lock.c index 506c2fe42d52..054997edd98b 100644 --- a/tools/perf/builtin-lock.c +++ b/tools/perf/builtin-lock.c @@ -58,16 +58,28 @@ static struct rb_root thread_stats; static bool combine_locks; static bool show_thread_stats; static bool show_lock_addrs; +static bool show_lock_owner; static bool use_bpf; static unsigned long bpf_map_entries = 10240; static int max_stack_depth = CONTENTION_STACK_DEPTH; static int stack_skip = CONTENTION_STACK_SKIP; static int print_nr_entries = INT_MAX / 2; +static LIST_HEAD(callstack_filters); + +struct callstack_filter { + struct list_head list; + char name[]; +}; static struct lock_filter filters; static enum lock_aggr_mode aggr_mode = LOCK_AGGR_ADDR; +static bool needs_callstack(void) +{ + return verbose > 0 || !list_empty(&callstack_filters); +} + static struct thread_stat *thread_stat_find(u32 tid) { struct rb_node *node; @@ -454,7 +466,7 @@ static struct lock_stat *pop_from_result(void) return container_of(node, struct lock_stat, rb); } -static struct lock_stat *lock_stat_find(u64 addr) +struct lock_stat *lock_stat_find(u64 addr) { struct hlist_head *entry = lockhashentry(addr); struct lock_stat *ret; @@ -466,7 +478,7 @@ static struct lock_stat *lock_stat_find(u64 addr) return NULL; } -static struct lock_stat *lock_stat_findnew(u64 addr, const char *name, int flags) +struct lock_stat *lock_stat_findnew(u64 addr, const char *name, int flags) { struct hlist_head *entry = lockhashentry(addr); struct lock_stat *ret, *new; @@ -498,6 +510,34 @@ alloc_failed: return NULL; } +bool match_callstack_filter(struct machine *machine, u64 *callstack) +{ + struct map *kmap; + struct symbol *sym; + u64 ip; + + if (list_empty(&callstack_filters)) + return true; + + for (int i = 0; i < max_stack_depth; i++) { + struct callstack_filter *filter; + + if (!callstack || !callstack[i]) + break; + + ip = callstack[i]; + sym = machine__find_kernel_symbol(machine, ip, &kmap); + if (sym == NULL) + continue; + + list_for_each_entry(filter, &callstack_filters, list) { + if (strstr(sym->name, filter->name)) + return true; + } + } + return false; +} + struct trace_lock_handler { /* it's used on CONFIG_LOCKDEP */ int (*acquire_event)(struct evsel *evsel, @@ -1059,12 +1099,6 @@ static int report_lock_contention_begin_event(struct evsel *evsel, ls = lock_stat_findnew(key, name, flags); if (!ls) return -ENOMEM; - - if (aggr_mode == LOCK_AGGR_CALLER && verbose > 0) { - ls->callstack = get_callstack(sample, max_stack_depth); - if (ls->callstack == NULL) - return -ENOMEM; - } } if (filters.nr_types) { @@ -1095,6 +1129,22 @@ static int report_lock_contention_begin_event(struct evsel *evsel, return 0; } + if (needs_callstack()) { + u64 *callstack = get_callstack(sample, max_stack_depth); + if (callstack == NULL) + return -ENOMEM; + + if (!match_callstack_filter(machine, callstack)) { + free(callstack); + return 0; + } + + if (ls->callstack == NULL) + ls->callstack = callstack; + else + free(callstack); + } + ts = thread_stat_findnew(sample->tid); if (!ts) return -ENOMEM; @@ -1567,7 +1617,8 @@ static void print_contention_result(struct lock_contention *con) switch (aggr_mode) { case LOCK_AGGR_TASK: - pr_info(" %10s %s\n\n", "pid", "comm"); + pr_info(" %10s %s\n\n", "pid", + show_lock_owner ? "owner" : "comm"); break; case LOCK_AGGR_CALLER: pr_info(" %10s %s\n\n", "type", "caller"); @@ -1607,7 +1658,8 @@ static void print_contention_result(struct lock_contention *con) case LOCK_AGGR_TASK: pid = st->addr; t = perf_session__findnew(session, pid); - pr_info(" %10d %s\n", pid, thread__comm_str(t)); + pr_info(" %10d %s\n", + pid, pid == -1 ? "Unknown" : thread__comm_str(t)); break; case LOCK_AGGR_ADDR: pr_info(" %016llx %s\n", (unsigned long long)st->addr, @@ -1719,6 +1771,37 @@ static void sighandler(int sig __maybe_unused) { } +static int check_lock_contention_options(const struct option *options, + const char * const *usage) + +{ + if (show_thread_stats && show_lock_addrs) { + pr_err("Cannot use thread and addr mode together\n"); + parse_options_usage(usage, options, "threads", 0); + parse_options_usage(NULL, options, "lock-addr", 0); + return -1; + } + + if (show_lock_owner && !use_bpf) { + pr_err("Lock owners are available only with BPF\n"); + parse_options_usage(usage, options, "lock-owner", 0); + parse_options_usage(NULL, options, "use-bpf", 0); + return -1; + } + + if (show_lock_owner && show_lock_addrs) { + pr_err("Cannot use owner and addr mode together\n"); + parse_options_usage(usage, options, "lock-owner", 0); + parse_options_usage(NULL, options, "lock-addr", 0); + return -1; + } + + if (show_lock_owner) + show_thread_stats = true; + + return 0; +} + static int __cmd_contention(int argc, const char **argv) { int err = -EINVAL; @@ -1743,6 +1826,8 @@ static int __cmd_contention(int argc, const char **argv) .max_stack = max_stack_depth, .stack_skip = stack_skip, .filters = &filters, + .save_callstack = needs_callstack(), + .owner = show_lock_owner, }; session = perf_session__new(use_bpf ? NULL : &data, &eops); @@ -1756,6 +1841,9 @@ static int __cmd_contention(int argc, const char **argv) con.aggr_mode = aggr_mode = show_thread_stats ? LOCK_AGGR_TASK : show_lock_addrs ? LOCK_AGGR_ADDR : LOCK_AGGR_CALLER; + if (con.aggr_mode == LOCK_AGGR_CALLER) + con.save_callstack = true; + /* for lock function check */ symbol_conf.sort_by_name = true; symbol_conf.allow_aliases = true; @@ -2123,6 +2211,33 @@ static int parse_lock_addr(const struct option *opt __maybe_unused, const char * return ret; } +static int parse_call_stack(const struct option *opt __maybe_unused, const char *str, + int unset __maybe_unused) +{ + char *s, *tmp, *tok; + int ret = 0; + + s = strdup(str); + if (s == NULL) + return -1; + + for (tok = strtok_r(s, ", ", &tmp); tok; tok = strtok_r(NULL, ", ", &tmp)) { + struct callstack_filter *entry; + + entry = malloc(sizeof(*entry) + strlen(tok) + 1); + if (entry == NULL) { + pr_err("Memory allocation failure\n"); + return -1; + } + + strcpy(entry->name, tok); + list_add_tail(&entry->list, &callstack_filters); + } + + free(s); + return ret; +} + int cmd_lock(int argc, const char **argv) { const struct option lock_options[] = { @@ -2190,6 +2305,9 @@ int cmd_lock(int argc, const char **argv) "Filter specific type of locks", parse_lock_type), OPT_CALLBACK('L', "lock-filter", NULL, "ADDRS/NAMES", "Filter specific address/symbol of locks", parse_lock_addr), + OPT_CALLBACK('S', "callstack-filter", NULL, "NAMES", + "Filter specific function in the callstack", parse_call_stack), + OPT_BOOLEAN('o', "lock-owner", &show_lock_owner, "show lock owners instead of waiters"), OPT_PARENT(lock_options) }; @@ -2260,14 +2378,9 @@ int cmd_lock(int argc, const char **argv) contention_usage, 0); } - if (show_thread_stats && show_lock_addrs) { - pr_err("Cannot use thread and addr mode together\n"); - parse_options_usage(contention_usage, contention_options, - "threads", 0); - parse_options_usage(NULL, contention_options, - "lock-addr", 0); + if (check_lock_contention_options(contention_options, + contention_usage) < 0) return -1; - } rc = __cmd_contention(argc, argv); } else { diff --git a/tools/perf/builtin-probe.c b/tools/perf/builtin-probe.c index ed73d0b89ca2..e72f6cea76f7 100644 --- a/tools/perf/builtin-probe.c +++ b/tools/perf/builtin-probe.c @@ -383,9 +383,18 @@ static int perf_add_probe_events(struct perf_probe_event *pevs, int npevs) /* Note that it is possible to skip all events because of blacklist */ if (event) { +#ifndef HAVE_LIBTRACEEVENT + pr_info("\nperf is not linked with libtraceevent, to use the new probe you can use tracefs:\n\n"); + pr_info("\tcd /sys/kernel/tracing/\n"); + pr_info("\techo 1 > events/%s/%s/enable\n", group, event); + pr_info("\techo 1 > tracing_on\n"); + pr_info("\tcat trace_pipe\n"); + pr_info("\tBefore removing the probe, echo 0 > events/%s/%s/enable\n", group, event); +#else /* Show how to use the event. */ pr_info("\nYou can now use it in all perf tools, such as:\n\n"); pr_info("\tperf record -e %s:%s -aR sleep 1\n\n", group, event); +#endif } out_cleanup: diff --git a/tools/perf/builtin-record.c b/tools/perf/builtin-record.c index 29dcd454b8e2..8374117e66f6 100644 --- a/tools/perf/builtin-record.c +++ b/tools/perf/builtin-record.c @@ -154,6 +154,7 @@ struct record { struct perf_tool tool; struct record_opts opts; u64 bytes_written; + u64 thread_bytes_written; struct perf_data data; struct auxtrace_record *itr; struct evlist *evlist; @@ -226,14 +227,7 @@ static bool switch_output_time(struct record *rec) static u64 record__bytes_written(struct record *rec) { - int t; - u64 bytes_written = rec->bytes_written; - struct record_thread *thread_data = rec->thread_data; - - for (t = 0; t < rec->nr_threads; t++) - bytes_written += thread_data[t].bytes_written; - - return bytes_written; + return rec->bytes_written + rec->thread_bytes_written; } static bool record__output_max_size_exceeded(struct record *rec) @@ -255,10 +249,12 @@ static int record__write(struct record *rec, struct mmap *map __maybe_unused, return -1; } - if (map && map->file) + if (map && map->file) { thread->bytes_written += size; - else + rec->thread_bytes_written += size; + } else { rec->bytes_written += size; + } if (record__output_max_size_exceeded(rec) && !done) { fprintf(stderr, "[ perf record: perf size limit reached (%" PRIu64 " KB)," diff --git a/tools/perf/builtin-script.c b/tools/perf/builtin-script.c index 69394ac0a20d..a792214d1af8 100644 --- a/tools/perf/builtin-script.c +++ b/tools/perf/builtin-script.c @@ -59,6 +59,7 @@ #include "util/dlfilter.h" #include "util/record.h" #include "util/util.h" +#include "util/cgroup.h" #include "perf.h" #include <linux/ctype.h> @@ -130,6 +131,8 @@ enum perf_output_field { PERF_OUTPUT_BRSTACKINSNLEN = 1ULL << 36, PERF_OUTPUT_MACHINE_PID = 1ULL << 37, PERF_OUTPUT_VCPU = 1ULL << 38, + PERF_OUTPUT_CGROUP = 1ULL << 39, + PERF_OUTPUT_RETIRE_LAT = 1ULL << 40, }; struct perf_script { @@ -200,6 +203,8 @@ struct output_option { {.str = "brstackinsnlen", .field = PERF_OUTPUT_BRSTACKINSNLEN}, {.str = "machine_pid", .field = PERF_OUTPUT_MACHINE_PID}, {.str = "vcpu", .field = PERF_OUTPUT_VCPU}, + {.str = "cgroup", .field = PERF_OUTPUT_CGROUP}, + {.str = "retire_lat", .field = PERF_OUTPUT_RETIRE_LAT}, }; enum { @@ -275,7 +280,7 @@ static struct { PERF_OUTPUT_ADDR | PERF_OUTPUT_DATA_SRC | PERF_OUTPUT_WEIGHT | PERF_OUTPUT_PHYS_ADDR | PERF_OUTPUT_DATA_PAGE_SIZE | PERF_OUTPUT_CODE_PAGE_SIZE | - PERF_OUTPUT_INS_LAT, + PERF_OUTPUT_INS_LAT | PERF_OUTPUT_RETIRE_LAT, .invalid_fields = PERF_OUTPUT_TRACE | PERF_OUTPUT_BPF_OUTPUT, }, @@ -542,6 +547,16 @@ static int evsel__check_attr(struct evsel *evsel, struct perf_session *session) evsel__check_stype(evsel, PERF_SAMPLE_WEIGHT_STRUCT, "WEIGHT_STRUCT", PERF_OUTPUT_INS_LAT)) return -EINVAL; + if (PRINT_FIELD(CGROUP) && + evsel__check_stype(evsel, PERF_SAMPLE_CGROUP, "CGROUP", PERF_OUTPUT_CGROUP)) { + pr_err("Hint: run 'perf record --all-cgroups ...'\n"); + return -EINVAL; + } + + if (PRINT_FIELD(RETIRE_LAT) && + evsel__check_stype(evsel, PERF_SAMPLE_WEIGHT_STRUCT, "WEIGHT_STRUCT", PERF_OUTPUT_RETIRE_LAT)) + return -EINVAL; + return 0; } @@ -880,12 +895,13 @@ mispred_str(struct branch_entry *br) static int print_bstack_flags(FILE *fp, struct branch_entry *br) { - return fprintf(fp, "/%c/%c/%c/%d/%s ", + return fprintf(fp, "/%c/%c/%c/%d/%s/%s ", mispred_str(br), br->flags.in_tx ? 'X' : '-', br->flags.abort ? 'A' : '-', br->flags.cycles, - get_branch_type(br)); + get_branch_type(br), + br->flags.spec ? branch_spec_desc(br->flags.spec) : "-"); } static int perf_sample__fprintf_brstack(struct perf_sample *sample, @@ -1301,7 +1317,7 @@ static int perf_sample__fprintf_brstackinsn(struct perf_sample *sample, goto out; /* - * Print final block upto sample + * Print final block up to sample * * Due to pipeline delays the LBRs might be missing a branch * or two, which can result in very large or negative blocks @@ -2178,6 +2194,9 @@ static void process_event(struct perf_script *script, if (PRINT_FIELD(INS_LAT)) fprintf(fp, "%16" PRIu16, sample->ins_lat); + if (PRINT_FIELD(RETIRE_LAT)) + fprintf(fp, "%16" PRIu16, sample->retire_lat); + if (PRINT_FIELD(IP)) { struct callchain_cursor *cursor = NULL; @@ -2220,6 +2239,17 @@ static void process_event(struct perf_script *script, if (PRINT_FIELD(CODE_PAGE_SIZE)) fprintf(fp, " %s", get_page_size_name(sample->code_page_size, str)); + if (PRINT_FIELD(CGROUP)) { + const char *cgrp_name; + struct cgroup *cgrp = cgroup__find(machine->env, + sample->cgroup); + if (cgrp != NULL) + cgrp_name = cgrp->name; + else + cgrp_name = "unknown"; + fprintf(fp, " %s", cgrp_name); + } + perf_sample__fprintf_ipc(sample, attr, fp); fprintf(fp, "\n"); @@ -3856,7 +3886,7 @@ int cmd_script(int argc, const char **argv) "brstacksym,flags,data_src,weight,bpf-output,brstackinsn," "brstackinsnlen,brstackoff,callindent,insn,insnlen,synth," "phys_addr,metric,misc,srccode,ipc,tod,data_page_size," - "code_page_size,ins_lat", + "code_page_size,ins_lat,machine_pid,vcpu,cgroup,retire_lat", parse_output_fields), OPT_BOOLEAN('a', "all-cpus", &system_wide, "system-wide collection from all CPUs"), diff --git a/tools/perf/builtin-stat.c b/tools/perf/builtin-stat.c index 9f3e4b257516..fa7c40956d0f 100644 --- a/tools/perf/builtin-stat.c +++ b/tools/perf/builtin-stat.c @@ -539,12 +539,7 @@ static int enable_counters(void) return err; } - /* - * We need to enable counters only if: - * - we don't have tracee (attaching to task or cpu) - * - we have initial delay configured - */ - if (!target__none(&target)) { + if (!target__enable_on_exec(&target)) { if (!all_counters_use_bpf) evlist__enable(evsel_list); } @@ -914,7 +909,7 @@ try_again_reset: return err; } - if (stat_config.initial_delay) { + if (target.initial_delay) { pr_info(EVLIST_DISABLED_MSG); } else { err = enable_counters(); @@ -926,8 +921,8 @@ try_again_reset: if (forks) evlist__start_workload(evsel_list); - if (stat_config.initial_delay > 0) { - usleep(stat_config.initial_delay * USEC_PER_MSEC); + if (target.initial_delay > 0) { + usleep(target.initial_delay * USEC_PER_MSEC); err = enable_counters(); if (err) return -1; @@ -1248,7 +1243,7 @@ static struct option stat_options[] = { "aggregate counts per thread", AGGR_THREAD), OPT_SET_UINT(0, "per-node", &stat_config.aggr_mode, "aggregate counts per numa node", AGGR_NODE), - OPT_INTEGER('D', "delay", &stat_config.initial_delay, + OPT_INTEGER('D', "delay", &target.initial_delay, "ms to wait before starting measurement after program start (-1: start with events disabled)"), OPT_CALLBACK_NOOPT(0, "metric-only", &stat_config.metric_only, NULL, "Only print computed metrics. No raw values", enable_metric_only), @@ -2524,7 +2519,6 @@ int cmd_stat(int argc, const char **argv) &stat_config.metric_events); zfree(&metrics); } - perf_stat__collect_metric_expr(evsel_list); perf_stat__init_shadow_stats(); if (add_default_attributes()) diff --git a/tools/perf/builtin-trace.c b/tools/perf/builtin-trace.c index d21fe0f32a6d..610fb60b1c0d 100644 --- a/tools/perf/builtin-trace.c +++ b/tools/perf/builtin-trace.c @@ -2731,10 +2731,8 @@ static size_t trace__fprintf_tp_fields(struct trace *trace, struct evsel *evsel, offset = format_field__intval(field, sample, evsel->needs_swap); syscall_arg.len = offset >> 16; offset &= 0xffff; -#ifdef HAVE_LIBTRACEEVENT_TEP_FIELD_IS_RELATIVE - if (field->flags & TEP_FIELD_IS_RELATIVE) + if (tep_field_is_relative(field->flags)) offset += field->offset + field->size; -#endif } val = (uintptr_t)(sample->raw_data + offset); diff --git a/tools/perf/perf-completion.sh b/tools/perf/perf-completion.sh index fdf75d45efff..978249d7868c 100644 --- a/tools/perf/perf-completion.sh +++ b/tools/perf/perf-completion.sh @@ -165,7 +165,12 @@ __perf_main () local cur1=${COMP_WORDS[COMP_CWORD]} local raw_evts=$($cmd list --raw-dump) - local arr s tmp result + local arr s tmp result cpu_evts + + # aarch64 doesn't have /sys/bus/event_source/devices/cpu/events + if [[ `uname -m` != aarch64 ]]; then + cpu_evts=$(ls /sys/bus/event_source/devices/cpu/events) + fi if [[ "$cur1" == */* && ${cur1#*/} =~ ^[A-Z] ]]; then OLD_IFS="$IFS" @@ -183,9 +188,9 @@ __perf_main () fi done - evts=${result}" "$(ls /sys/bus/event_source/devices/cpu/events) + evts=${result}" "${cpu_evts} else - evts=${raw_evts}" "$(ls /sys/bus/event_source/devices/cpu/events) + evts=${raw_evts}" "${cpu_evts} fi if [[ "$cur1" == , ]]; then diff --git a/tools/perf/pmu-events/Build b/tools/perf/pmu-events/Build index 15b9e8fdbffa..150765f2baee 100644 --- a/tools/perf/pmu-events/Build +++ b/tools/perf/pmu-events/Build @@ -6,10 +6,16 @@ JDIR_TEST = pmu-events/arch/test JSON_TEST = $(shell [ -d $(JDIR_TEST) ] && \ find $(JDIR_TEST) -name '*.json') JEVENTS_PY = pmu-events/jevents.py +METRIC_PY = pmu-events/metric.py +METRIC_TEST_PY = pmu-events/metric_test.py +EMPTY_PMU_EVENTS_C = pmu-events/empty-pmu-events.c +PMU_EVENTS_C = $(OUTPUT)pmu-events/pmu-events.c +METRIC_TEST_LOG = $(OUTPUT)pmu-events/metric_test.log ifeq ($(JEVENTS_ARCH),) JEVENTS_ARCH=$(SRCARCH) endif +JEVENTS_MODEL ?= all # # Locate/process JSON files in pmu-events/arch/ @@ -17,11 +23,15 @@ endif # ifeq ($(NO_JEVENTS),1) -$(OUTPUT)pmu-events/pmu-events.c: pmu-events/empty-pmu-events.c +$(PMU_EVENTS_C): $(EMPTY_PMU_EVENTS_C) $(call rule_mkdir) $(Q)$(call echo-cmd,gen)cp $< $@ else -$(OUTPUT)pmu-events/pmu-events.c: $(JSON) $(JSON_TEST) $(JEVENTS_PY) pmu-events/metric.py +$(METRIC_TEST_LOG): $(METRIC_TEST_PY) $(METRIC_PY) $(call rule_mkdir) - $(Q)$(call echo-cmd,gen)$(PYTHON) $(JEVENTS_PY) $(JEVENTS_ARCH) pmu-events/arch $@ + $(Q)$(call echo-cmd,test)$(PYTHON) $< 2> $@ || (cat $@ && false) + +$(PMU_EVENTS_C): $(JSON) $(JSON_TEST) $(JEVENTS_PY) $(METRIC_PY) $(METRIC_TEST_LOG) + $(call rule_mkdir) + $(Q)$(call echo-cmd,gen)$(PYTHON) $(JEVENTS_PY) $(JEVENTS_ARCH) $(JEVENTS_MODEL) pmu-events/arch $@ endif diff --git a/tools/perf/pmu-events/arch/arm64/arm/neoverse-n2-v2/metrics.json b/tools/perf/pmu-events/arch/arm64/arm/neoverse-n2-v2/metrics.json new file mode 100644 index 000000000000..8ad15b726dca --- /dev/null +++ b/tools/perf/pmu-events/arch/arm64/arm/neoverse-n2-v2/metrics.json @@ -0,0 +1,273 @@ +[ + { + "ArchStdEvent": "FRONTEND_BOUND", + "MetricExpr": "((stall_slot_frontend) if (#slots - 5) else (stall_slot_frontend - cpu_cycles)) / (#slots * cpu_cycles)" + }, + { + "ArchStdEvent": "BAD_SPECULATION", + "MetricExpr": "(1 - op_retired / op_spec) * (1 - (stall_slot if (#slots - 5) else (stall_slot - cpu_cycles)) / (#slots * cpu_cycles))" + }, + { + "ArchStdEvent": "RETIRING", + "MetricExpr": "(op_retired / op_spec) * (1 - (stall_slot if (#slots - 5) else (stall_slot - cpu_cycles)) / (#slots * cpu_cycles))" + }, + { + "ArchStdEvent": "BACKEND_BOUND" + }, + { + "MetricExpr": "L1D_TLB_REFILL / L1D_TLB", + "BriefDescription": "The rate of L1D TLB refill to the overall L1D TLB lookups", + "MetricGroup": "TLB", + "MetricName": "l1d_tlb_miss_rate", + "ScaleUnit": "100%" + }, + { + "MetricExpr": "L1I_TLB_REFILL / L1I_TLB", + "BriefDescription": "The rate of L1I TLB refill to the overall L1I TLB lookups", + "MetricGroup": "TLB", + "MetricName": "l1i_tlb_miss_rate", + "ScaleUnit": "100%" + }, + { + "MetricExpr": "L2D_TLB_REFILL / L2D_TLB", + "BriefDescription": "The rate of L2D TLB refill to the overall L2D TLB lookups", + "MetricGroup": "TLB", + "MetricName": "l2_tlb_miss_rate", + "ScaleUnit": "100%" + }, + { + "MetricExpr": "DTLB_WALK / INST_RETIRED * 1000", + "BriefDescription": "The rate of TLB Walks per kilo instructions for data accesses", + "MetricGroup": "TLB", + "MetricName": "dtlb_mpki", + "ScaleUnit": "1MPKI" + }, + { + "MetricExpr": "DTLB_WALK / L1D_TLB", + "BriefDescription": "The rate of DTLB Walks to the overall L1D TLB lookups", + "MetricGroup": "TLB", + "MetricName": "dtlb_walk_rate", + "ScaleUnit": "100%" + }, + { + "MetricExpr": "ITLB_WALK / INST_RETIRED * 1000", + "BriefDescription": "The rate of TLB Walks per kilo instructions for instruction accesses", + "MetricGroup": "TLB", + "MetricName": "itlb_mpki", + "ScaleUnit": "1MPKI" + }, + { + "MetricExpr": "ITLB_WALK / L1I_TLB", + "BriefDescription": "The rate of ITLB Walks to the overall L1I TLB lookups", + "MetricGroup": "TLB", + "MetricName": "itlb_walk_rate", + "ScaleUnit": "100%" + }, + { + "MetricExpr": "L1I_CACHE_REFILL / INST_RETIRED * 1000", + "BriefDescription": "The rate of L1 I-Cache misses per kilo instructions", + "MetricGroup": "Cache", + "MetricName": "l1i_cache_mpki", + "ScaleUnit": "1MPKI" + }, + { + "MetricExpr": "L1I_CACHE_REFILL / L1I_CACHE", + "BriefDescription": "The rate of L1 I-Cache misses to the overall L1 I-Cache", + "MetricGroup": "Cache", + "MetricName": "l1i_cache_miss_rate", + "ScaleUnit": "100%" + }, + { + "MetricExpr": "L1D_CACHE_REFILL / INST_RETIRED * 1000", + "BriefDescription": "The rate of L1 D-Cache misses per kilo instructions", + "MetricGroup": "Cache", + "MetricName": "l1d_cache_mpki", + "ScaleUnit": "1MPKI" + }, + { + "MetricExpr": "L1D_CACHE_REFILL / L1D_CACHE", + "BriefDescription": "The rate of L1 D-Cache misses to the overall L1 D-Cache", + "MetricGroup": "Cache", + "MetricName": "l1d_cache_miss_rate", + "ScaleUnit": "100%" + }, + { + "MetricExpr": "L2D_CACHE_REFILL / INST_RETIRED * 1000", + "BriefDescription": "The rate of L2 D-Cache misses per kilo instructions", + "MetricGroup": "Cache", + "MetricName": "l2d_cache_mpki", + "ScaleUnit": "1MPKI" + }, + { + "MetricExpr": "L2D_CACHE_REFILL / L2D_CACHE", + "BriefDescription": "The rate of L2 D-Cache misses to the overall L2 D-Cache", + "MetricGroup": "Cache", + "MetricName": "l2d_cache_miss_rate", + "ScaleUnit": "100%" + }, + { + "MetricExpr": "L3D_CACHE_REFILL / INST_RETIRED * 1000", + "BriefDescription": "The rate of L3 D-Cache misses per kilo instructions", + "MetricGroup": "Cache", + "MetricName": "l3d_cache_mpki", + "ScaleUnit": "1MPKI" + }, + { + "MetricExpr": "L3D_CACHE_REFILL / L3D_CACHE", + "BriefDescription": "The rate of L3 D-Cache misses to the overall L3 D-Cache", + "MetricGroup": "Cache", + "MetricName": "l3d_cache_miss_rate", + "ScaleUnit": "100%" + }, + { + "MetricExpr": "LL_CACHE_MISS_RD / INST_RETIRED * 1000", + "BriefDescription": "The rate of LL Cache read misses per kilo instructions", + "MetricGroup": "Cache", + "MetricName": "ll_cache_read_mpki", + "ScaleUnit": "1MPKI" + }, + { + "MetricExpr": "LL_CACHE_MISS_RD / LL_CACHE_RD", + "BriefDescription": "The rate of LL Cache read misses to the overall LL Cache read", + "MetricGroup": "Cache", + "MetricName": "ll_cache_read_miss_rate", + "ScaleUnit": "100%" + }, + { + "MetricExpr": "(LL_CACHE_RD - LL_CACHE_MISS_RD) / LL_CACHE_RD", + "BriefDescription": "The rate of LL Cache read hit to the overall LL Cache read", + "MetricGroup": "Cache", + "MetricName": "ll_cache_read_hit_rate", + "ScaleUnit": "100%" + }, + { + "MetricExpr": "BR_MIS_PRED_RETIRED / INST_RETIRED * 1000", + "BriefDescription": "The rate of branches mis-predicted per kilo instructions", + "MetricGroup": "Branch", + "MetricName": "branch_mpki", + "ScaleUnit": "1MPKI" + }, + { + "MetricExpr": "BR_RETIRED / INST_RETIRED * 1000", + "BriefDescription": "The rate of branches retired per kilo instructions", + "MetricGroup": "Branch", + "MetricName": "branch_pki", + "ScaleUnit": "1PKI" + }, + { + "MetricExpr": "BR_MIS_PRED_RETIRED / BR_RETIRED", + "BriefDescription": "The rate of branches mis-predited to the overall branches", + "MetricGroup": "Branch", + "MetricName": "branch_miss_pred_rate", + "ScaleUnit": "100%" + }, + { + "MetricExpr": "instructions / CPU_CYCLES", + "BriefDescription": "The average number of instructions executed for each cycle.", + "MetricGroup": "PEutilization", + "MetricName": "ipc" + }, + { + "MetricExpr": "ipc / 5", + "BriefDescription": "IPC percentage of peak. The peak of IPC is 5.", + "MetricGroup": "PEutilization", + "MetricName": "ipc_rate", + "ScaleUnit": "100%" + }, + { + "MetricExpr": "INST_RETIRED / CPU_CYCLES", + "BriefDescription": "Architecturally executed Instructions Per Cycle (IPC)", + "MetricGroup": "PEutilization", + "MetricName": "retired_ipc" + }, + { + "MetricExpr": "INST_SPEC / CPU_CYCLES", + "BriefDescription": "Speculatively executed Instructions Per Cycle (IPC)", + "MetricGroup": "PEutilization", + "MetricName": "spec_ipc" + }, + { + "MetricExpr": "OP_RETIRED / OP_SPEC", + "BriefDescription": "Of all the micro-operations issued, what percentage are retired(committed)", + "MetricGroup": "PEutilization", + "MetricName": "retired_rate", + "ScaleUnit": "100%" + }, + { + "MetricExpr": "1 - OP_RETIRED / OP_SPEC", + "BriefDescription": "Of all the micro-operations issued, what percentage are not retired(committed)", + "MetricGroup": "PEutilization", + "MetricName": "wasted_rate", + "ScaleUnit": "100%" + }, + { + "MetricExpr": "OP_RETIRED / OP_SPEC * (1 - (STALL_SLOT if (#slots - 5) else (STALL_SLOT - CPU_CYCLES)) / (#slots * CPU_CYCLES))", + "BriefDescription": "The truly effective ratio of micro-operations executed by the CPU, which means that misprediction and stall are not included", + "MetricGroup": "PEutilization", + "MetricName": "cpu_utilization", + "ScaleUnit": "100%" + }, + { + "MetricExpr": "LD_SPEC / INST_SPEC", + "BriefDescription": "The rate of load instructions speculatively executed to overall instructions speclatively executed", + "MetricGroup": "InstructionMix", + "MetricName": "load_spec_rate", + "ScaleUnit": "100%" + }, + { + "MetricExpr": "ST_SPEC / INST_SPEC", + "BriefDescription": "The rate of store instructions speculatively executed to overall instructions speclatively executed", + "MetricGroup": "InstructionMix", + "MetricName": "store_spec_rate", + "ScaleUnit": "100%" + }, + { + "MetricExpr": "DP_SPEC / INST_SPEC", + "BriefDescription": "The rate of integer data-processing instructions speculatively executed to overall instructions speclatively executed", + "MetricGroup": "InstructionMix", + "MetricName": "data_process_spec_rate", + "ScaleUnit": "100%" + }, + { + "MetricExpr": "ASE_SPEC / INST_SPEC", + "BriefDescription": "The rate of advanced SIMD instructions speculatively executed to overall instructions speclatively executed", + "MetricGroup": "InstructionMix", + "MetricName": "advanced_simd_spec_rate", + "ScaleUnit": "100%" + }, + { + "MetricExpr": "VFP_SPEC / INST_SPEC", + "BriefDescription": "The rate of floating point instructions speculatively executed to overall instructions speclatively executed", + "MetricGroup": "InstructionMix", + "MetricName": "float_point_spec_rate", + "ScaleUnit": "100%" + }, + { + "MetricExpr": "CRYPTO_SPEC / INST_SPEC", + "BriefDescription": "The rate of crypto instructions speculatively executed to overall instructions speclatively executed", + "MetricGroup": "InstructionMix", + "MetricName": "crypto_spec_rate", + "ScaleUnit": "100%" + }, + { + "MetricExpr": "BR_IMMED_SPEC / INST_SPEC", + "BriefDescription": "The rate of branch immediate instructions speculatively executed to overall instructions speclatively executed", + "MetricGroup": "InstructionMix", + "MetricName": "branch_immed_spec_rate", + "ScaleUnit": "100%" + }, + { + "MetricExpr": "BR_RETURN_SPEC / INST_SPEC", + "BriefDescription": "The rate of procedure return instructions speculatively executed to overall instructions speclatively executed", + "MetricGroup": "InstructionMix", + "MetricName": "branch_return_spec_rate", + "ScaleUnit": "100%" + }, + { + "MetricExpr": "BR_INDIRECT_SPEC / INST_SPEC", + "BriefDescription": "The rate of indirect branch instructions speculatively executed to overall instructions speclatively executed", + "MetricGroup": "InstructionMix", + "MetricName": "branch_indirect_spec_rate", + "ScaleUnit": "100%" + } +] diff --git a/tools/perf/pmu-events/arch/arm64/sbsa.json b/tools/perf/pmu-events/arch/arm64/sbsa.json new file mode 100644 index 000000000000..f678c37ea9c3 --- /dev/null +++ b/tools/perf/pmu-events/arch/arm64/sbsa.json @@ -0,0 +1,30 @@ +[ + { + "MetricExpr": "stall_slot_frontend / (#slots * cpu_cycles)", + "BriefDescription": "Frontend bound L1 topdown metric", + "MetricGroup": "TopdownL1", + "MetricName": "frontend_bound", + "ScaleUnit": "100%" + }, + { + "MetricExpr": "(1 - op_retired / op_spec) * (1 - stall_slot / (#slots * cpu_cycles))", + "BriefDescription": "Bad speculation L1 topdown metric", + "MetricGroup": "TopdownL1", + "MetricName": "bad_speculation", + "ScaleUnit": "100%" + }, + { + "MetricExpr": "(op_retired / op_spec) * (1 - stall_slot / (#slots * cpu_cycles))", + "BriefDescription": "Retiring L1 topdown metric", + "MetricGroup": "TopdownL1", + "MetricName": "retiring", + "ScaleUnit": "100%" + }, + { + "MetricExpr": "stall_slot_backend / (#slots * cpu_cycles)", + "BriefDescription": "Backend Bound L1 topdown metric", + "MetricGroup": "TopdownL1", + "MetricName": "backend_bound", + "ScaleUnit": "100%" + } +] diff --git a/tools/perf/pmu-events/arch/powerpc/power10/metrics.json b/tools/perf/pmu-events/arch/powerpc/power10/metrics.json index b57526fa44f2..6f53583a0c62 100644 --- a/tools/perf/pmu-events/arch/powerpc/power10/metrics.json +++ b/tools/perf/pmu-events/arch/powerpc/power10/metrics.json @@ -15,7 +15,7 @@ { "BriefDescription": "Average cycles per completed instruction when dispatch was stalled for any reason", "MetricExpr": "PM_DISP_STALL_CYC / PM_RUN_INST_CMPL", - "MetricGroup": "CPI", + "MetricGroup": "CPI;CPI_STALL_RATIO", "MetricName": "DISPATCHED_CPI" }, { @@ -147,13 +147,13 @@ { "BriefDescription": "Average cycles per completed instruction when the NTC instruction has been dispatched but not issued for any reason", "MetricExpr": "PM_ISSUE_STALL / PM_RUN_INST_CMPL", - "MetricGroup": "CPI", + "MetricGroup": "CPI;CPI_STALL_RATIO", "MetricName": "ISSUE_STALL_CPI" }, { "BriefDescription": "Average cycles per completed instruction when the NTC instruction is waiting to be finished in one of the execution units", "MetricExpr": "PM_EXEC_STALL / PM_RUN_INST_CMPL", - "MetricGroup": "CPI", + "MetricGroup": "CPI;CPI_STALL_RATIO", "MetricName": "EXECUTION_STALL_CPI" }, { @@ -309,7 +309,7 @@ { "BriefDescription": "Average cycles per completed instruction when the NTC instruction cannot complete because the thread was blocked", "MetricExpr": "PM_CMPL_STALL / PM_RUN_INST_CMPL", - "MetricGroup": "CPI", + "MetricGroup": "CPI;CPI_STALL_RATIO", "MetricName": "COMPLETION_STALL_CPI" }, { diff --git a/tools/perf/pmu-events/arch/powerpc/power10/others.json b/tools/perf/pmu-events/arch/powerpc/power10/others.json index 7d0de1a2860b..a771e4b6bec5 100644 --- a/tools/perf/pmu-events/arch/powerpc/power10/others.json +++ b/tools/perf/pmu-events/arch/powerpc/power10/others.json @@ -265,7 +265,7 @@ "BriefDescription": "Load Missed L1, counted at finish time." }, { - "EventCode": "0x400FA", + "EventCode": "0x500FA", "EventName": "PM_RUN_INST_CMPL", "BriefDescription": "Completed PowerPC instructions gated by the run latch." } diff --git a/tools/perf/pmu-events/arch/x86/mapfile.csv b/tools/perf/pmu-events/arch/x86/mapfile.csv index 711a4ef05fdf..5facdac6fe8e 100644 --- a/tools/perf/pmu-events/arch/x86/mapfile.csv +++ b/tools/perf/pmu-events/arch/x86/mapfile.csv @@ -21,7 +21,7 @@ GenuineIntel-6-A[AC],v1.00,meteorlake,core GenuineIntel-6-1[AEF],v3,nehalemep,core GenuineIntel-6-2E,v3,nehalemex,core GenuineIntel-6-2A,v17,sandybridge,core -GenuineIntel-6-8F,v1.09,sapphirerapids,core +GenuineIntel-6-(8F|CF),v1.09,sapphirerapids,core GenuineIntel-6-(37|4A|4C|4D|5A),v14,silvermont,core GenuineIntel-6-(4E|5E|8E|9E|A5|A6),v53,skylake,core GenuineIntel-6-55-[01234],v1.28,skylakex,core diff --git a/tools/perf/pmu-events/empty-pmu-events.c b/tools/perf/pmu-events/empty-pmu-events.c index 480e8f0d30c8..a938b74cf487 100644 --- a/tools/perf/pmu-events/empty-pmu-events.c +++ b/tools/perf/pmu-events/empty-pmu-events.c @@ -11,7 +11,7 @@ #include <string.h> #include <stddef.h> -static const struct pmu_event pme_test_soc_cpu[] = { +static const struct pmu_event pmu_events__test_soc_cpu[] = { { .name = "l3_cache_rd", .event = "event=0x40", @@ -106,6 +106,14 @@ static const struct pmu_event pme_test_soc_cpu[] = { .topic = "branch", }, { + .name = 0, + .event = 0, + .desc = 0, + }, +}; + +static const struct pmu_metric pmu_metrics__test_soc_cpu[] = { + { .metric_expr = "1 / IPC", .metric_name = "CPI", }, @@ -170,9 +178,8 @@ static const struct pmu_event pme_test_soc_cpu[] = { .metric_name = "L1D_Cache_Fill_BW", }, { - .name = 0, - .event = 0, - .desc = 0, + .metric_expr = 0, + .metric_name = 0, }, }; @@ -181,6 +188,11 @@ struct pmu_events_table { const struct pmu_event *entries; }; +/* Struct used to make the PMU metric table implementation opaque to callers. */ +struct pmu_metrics_table { + const struct pmu_metric *entries; +}; + /* * 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. @@ -192,7 +204,8 @@ struct pmu_events_table { struct pmu_events_map { const char *arch; const char *cpuid; - const struct pmu_events_table table; + const struct pmu_events_table event_table; + const struct pmu_metrics_table metric_table; }; /* @@ -203,12 +216,14 @@ static const struct pmu_events_map pmu_events_map[] = { { .arch = "testarch", .cpuid = "testcpu", - .table = { pme_test_soc_cpu }, + .event_table = { pmu_events__test_soc_cpu }, + .metric_table = { pmu_metrics__test_soc_cpu }, }, { .arch = 0, .cpuid = 0, - .table = { 0 }, + .event_table = { 0 }, + .metric_table = { 0 }, }, }; @@ -254,9 +269,7 @@ static const struct pmu_sys_events pmu_sys_event_tables[] = { int pmu_events_table_for_each_event(const struct pmu_events_table *table, pmu_event_iter_fn fn, void *data) { - for (const struct pmu_event *pe = &table->entries[0]; - pe->name || pe->metric_group || pe->metric_name; - pe++) { + for (const struct pmu_event *pe = &table->entries[0]; pe->name; pe++) { int ret = fn(pe, table, data); if (ret) @@ -265,7 +278,19 @@ int pmu_events_table_for_each_event(const struct pmu_events_table *table, pmu_ev return 0; } -const struct pmu_events_table *perf_pmu__find_table(struct perf_pmu *pmu) +int pmu_metrics_table_for_each_metric(const struct pmu_metrics_table *table, pmu_metric_iter_fn fn, + void *data) +{ + for (const struct pmu_metric *pm = &table->entries[0]; pm->metric_expr; pm++) { + int ret = fn(pm, table, data); + + if (ret) + return ret; + } + return 0; +} + +const struct pmu_events_table *perf_pmu__find_events_table(struct perf_pmu *pmu) { const struct pmu_events_table *table = NULL; char *cpuid = perf_pmu__getcpuid(pmu); @@ -285,7 +310,35 @@ const struct pmu_events_table *perf_pmu__find_table(struct perf_pmu *pmu) break; if (!strcmp_cpuid_str(map->cpuid, cpuid)) { - table = &map->table; + table = &map->event_table; + break; + } + } + free(cpuid); + return table; +} + +const struct pmu_metrics_table *perf_pmu__find_metrics_table(struct perf_pmu *pmu) +{ + const struct pmu_metrics_table *table = NULL; + char *cpuid = perf_pmu__getcpuid(pmu); + int i; + + /* on some platforms which uses cpus map, cpuid can be NULL for + * PMUs other than CORE PMUs. + */ + if (!cpuid) + return NULL; + + i = 0; + for (;;) { + const struct pmu_events_map *map = &pmu_events_map[i++]; + + if (!map->cpuid) + break; + + if (!strcmp_cpuid_str(map->cpuid, cpuid)) { + table = &map->metric_table; break; } } @@ -299,17 +352,39 @@ const struct pmu_events_table *find_core_events_table(const char *arch, const ch tables->arch; tables++) { if (!strcmp(tables->arch, arch) && !strcmp_cpuid_str(tables->cpuid, cpuid)) - return &tables->table; + return &tables->event_table; + } + return NULL; +} + +const struct pmu_metrics_table *find_core_metrics_table(const char *arch, const char *cpuid) +{ + for (const struct pmu_events_map *tables = &pmu_events_map[0]; + tables->arch; + tables++) { + if (!strcmp(tables->arch, arch) && !strcmp_cpuid_str(tables->cpuid, cpuid)) + return &tables->metric_table; } return NULL; } int pmu_for_each_core_event(pmu_event_iter_fn fn, void *data) { + for (const struct pmu_events_map *tables = &pmu_events_map[0]; tables->arch; tables++) { + int ret = pmu_events_table_for_each_event(&tables->event_table, fn, data); + + if (ret) + return ret; + } + return 0; +} + +int pmu_for_each_core_metric(pmu_metric_iter_fn fn, void *data) +{ for (const struct pmu_events_map *tables = &pmu_events_map[0]; tables->arch; tables++) { - int ret = pmu_events_table_for_each_event(&tables->table, fn, data); + int ret = pmu_metrics_table_for_each_metric(&tables->metric_table, fn, data); if (ret) return ret; @@ -340,3 +415,8 @@ int pmu_for_each_sys_event(pmu_event_iter_fn fn, void *data) } return 0; } + +int pmu_for_each_sys_metric(pmu_metric_iter_fn fn __maybe_unused, void *data __maybe_unused) +{ + return 0; +} diff --git a/tools/perf/pmu-events/jevents.py b/tools/perf/pmu-events/jevents.py index 4c398e0eeb2f..2bcd07ce609f 100755 --- a/tools/perf/pmu-events/jevents.py +++ b/tools/perf/pmu-events/jevents.py @@ -3,6 +3,7 @@ """Convert directories of JSON events to C code.""" import argparse import csv +from functools import lru_cache import json import metric import os @@ -12,30 +13,47 @@ import collections # Global command line arguments. _args = None +# List of regular event tables. +_event_tables = [] # List of event tables generated from "/sys" directories. _sys_event_tables = [] +# List of regular metric tables. +_metric_tables = [] +# List of metric tables generated from "/sys" directories. +_sys_metric_tables = [] +# Mapping between sys event table names and sys metric table names. +_sys_event_table_to_metric_table_mapping = {} # Map from an event name to an architecture standard # JsonEvent. Architecture standard events are in json files in the top # f'{_args.starting_dir}/{_args.arch}' directory. _arch_std_events = {} -# Track whether an events table is currently being defined and needs closing. -_close_table = False # Events to write out when the table is closed _pending_events = [] +# Name of events table to be written out +_pending_events_tblname = None +# Metrics to write out when the table is closed +_pending_metrics = [] +# Name of metrics table to be written out +_pending_metrics_tblname = None # Global BigCString shared by all structures. _bcs = None # Order specific JsonEvent attributes will be visited. _json_event_attributes = [ # cmp_sevent related attributes. - 'name', 'pmu', 'topic', 'desc', 'metric_name', 'metric_group', + 'name', 'pmu', 'topic', 'desc', # Seems useful, put it early. 'event', # Short things in alphabetical order. 'aggr_mode', 'compat', 'deprecated', 'perpkg', 'unit', # Longer things (the last won't be iterated over during decompress). - 'metric_constraint', 'metric_expr', 'long_desc' + 'long_desc' ] +# Attributes that are in pmu_metric rather than pmu_event. +_json_metric_attributes = [ + 'metric_name', 'metric_group', 'metric_constraint', 'metric_expr', 'desc', + 'long_desc', 'unit', 'compat', 'aggr_mode' +] def removesuffix(s: str, suffix: str) -> str: """Remove the suffix from a string @@ -46,14 +64,16 @@ def removesuffix(s: str, suffix: str) -> str: return s[0:-len(suffix)] if s.endswith(suffix) else s -def file_name_to_table_name(parents: Sequence[str], dirname: str) -> str: +def file_name_to_table_name(prefix: str, parents: Sequence[str], + dirname: str) -> str: """Generate a C table name from directory names.""" - tblname = 'pme' + tblname = prefix for p in parents: tblname += '_' + p tblname += '_' + dirname return tblname.replace('-', '_') + def c_len(s: str) -> int: """Return the length of s a C string @@ -271,7 +291,7 @@ class JsonEvent: self.metric_constraint = jd.get('MetricConstraint') self.metric_expr = None if 'MetricExpr' in jd: - self.metric_expr = metric.ParsePerfJson(jd['MetricExpr']).Simplify() + self.metric_expr = metric.ParsePerfJson(jd['MetricExpr']).Simplify() arch_std = jd.get('ArchStdEvent') if precise and self.desc and '(Precise Event)' not in self.desc: @@ -320,35 +340,46 @@ class JsonEvent: s += f'\t{attr} = {value},\n' return s + '}' - def build_c_string(self) -> str: + def build_c_string(self, metric: bool) -> str: s = '' - for attr in _json_event_attributes: + for attr in _json_metric_attributes if metric else _json_event_attributes: x = getattr(self, attr) - if x and attr == 'metric_expr': + if metric and x and attr == 'metric_expr': # Convert parsed metric expressions into a string. Slashes # must be doubled in the file. x = x.ToPerfJson().replace('\\', '\\\\') s += f'{x}\\000' if x else '\\000' return s - def to_c_string(self) -> str: + def to_c_string(self, metric: bool) -> str: """Representation of the event as a C struct initializer.""" - s = self.build_c_string() + s = self.build_c_string(metric) return f'{{ { _bcs.offsets[s] } }}, /* {s} */\n' +@lru_cache(maxsize=None) def read_json_events(path: str, topic: str) -> Sequence[JsonEvent]: """Read json events from the specified file.""" - try: - result = json.load(open(path), object_hook=JsonEvent) + events = json.load(open(path), object_hook=JsonEvent) except BaseException as err: print(f"Exception processing {path}") raise - for event in result: + metrics: list[Tuple[str, metric.Expression]] = [] + for event in events: event.topic = topic - return result + if event.metric_name and '-' not in event.metric_name: + metrics.append((event.metric_name, event.metric_expr)) + updates = metric.RewriteMetricsInTermsOfOthers(metrics) + if updates: + for event in events: + if event.metric_name in updates: + # print(f'Updated {event.metric_name} from\n"{event.metric_expr}"\n' + # f'to\n"{updates[event.metric_name]}"') + event.metric_expr = updates[event.metric_name] + + return events def preprocess_arch_std_files(archpath: str) -> None: """Read in all architecture standard events.""" @@ -358,26 +389,20 @@ def preprocess_arch_std_files(archpath: str) -> None: for event in read_json_events(item.path, topic=''): if event.name: _arch_std_events[event.name.lower()] = event - - -def print_events_table_prefix(tblname: str) -> None: - """Called when a new events table is started.""" - global _close_table - if _close_table: - raise IOError('Printing table prefix but last table has no suffix') - _args.output_file.write(f'static const struct compact_pmu_event {tblname}[] = {{\n') - _close_table = True + if event.metric_name: + _arch_std_events[event.metric_name.lower()] = event def add_events_table_entries(item: os.DirEntry, topic: str) -> None: """Add contents of file to _pending_events table.""" - if not _close_table: - raise IOError('Table entries missing prefix') for e in read_json_events(item.path, topic): - _pending_events.append(e) + if e.name: + _pending_events.append(e) + if e.metric_name: + _pending_metrics.append(e) -def print_events_table_suffix() -> None: +def print_pending_events() -> None: """Optionally close events table.""" def event_cmp_key(j: JsonEvent) -> Tuple[bool, str, str, str, str]: @@ -389,17 +414,58 @@ def print_events_table_suffix() -> None: return (j.desc is not None, fix_none(j.topic), fix_none(j.name), fix_none(j.pmu), fix_none(j.metric_name)) - global _close_table - if not _close_table: + global _pending_events + if not _pending_events: return - global _pending_events + global _pending_events_tblname + if _pending_events_tblname.endswith('_sys'): + global _sys_event_tables + _sys_event_tables.append(_pending_events_tblname) + else: + global event_tables + _event_tables.append(_pending_events_tblname) + + _args.output_file.write( + f'static const struct compact_pmu_event {_pending_events_tblname}[] = {{\n') + for event in sorted(_pending_events, key=event_cmp_key): - _args.output_file.write(event.to_c_string()) - _pending_events = [] + _args.output_file.write(event.to_c_string(metric=False)) + _pending_events = [] + + _args.output_file.write('};\n\n') + +def print_pending_metrics() -> None: + """Optionally close metrics table.""" + + def metric_cmp_key(j: JsonEvent) -> Tuple[bool, str, str]: + def fix_none(s: Optional[str]) -> str: + if s is None: + return '' + return s + + return (j.desc is not None, fix_none(j.pmu), fix_none(j.metric_name)) + + global _pending_metrics + if not _pending_metrics: + return + + global _pending_metrics_tblname + if _pending_metrics_tblname.endswith('_sys'): + global _sys_metric_tables + _sys_metric_tables.append(_pending_metrics_tblname) + else: + global metric_tables + _metric_tables.append(_pending_metrics_tblname) + + _args.output_file.write( + f'static const struct compact_pmu_event {_pending_metrics_tblname}[] = {{\n') + + for metric in sorted(_pending_metrics, key=metric_cmp_key): + _args.output_file.write(metric.to_c_string(metric=True)) + _pending_metrics = [] _args.output_file.write('};\n\n') - _close_table = False def get_topic(topic: str) -> str: if topic.endswith('metrics.json'): @@ -423,12 +489,13 @@ def preprocess_one_file(parents: Sequence[str], item: os.DirEntry) -> None: topic = get_topic(item.name) for event in read_json_events(item.path, topic): - _bcs.add(event.build_c_string()) + if event.name: + _bcs.add(event.build_c_string(metric=False)) + if event.metric_name: + _bcs.add(event.build_c_string(metric=True)) def process_one_file(parents: Sequence[str], item: os.DirEntry) -> None: """Process a JSON file during the main walk.""" - global _sys_event_tables - def is_leaf_dir(path: str) -> bool: for item in os.scandir(path): if item.is_dir(): @@ -437,12 +504,16 @@ def process_one_file(parents: Sequence[str], item: os.DirEntry) -> None: # model directory, reset topic if item.is_dir() and is_leaf_dir(item.path): - print_events_table_suffix() + print_pending_events() + print_pending_metrics() + + global _pending_events_tblname + _pending_events_tblname = file_name_to_table_name('pmu_events_', parents, item.name) + global _pending_metrics_tblname + _pending_metrics_tblname = file_name_to_table_name('pmu_metrics_', parents, item.name) - tblname = file_name_to_table_name(parents, item.name) if item.name == 'sys': - _sys_event_tables.append(tblname) - print_events_table_prefix(tblname) + _sys_event_table_to_metric_table_mapping[_pending_events_tblname] = _pending_metrics_tblname return # base dir or too deep @@ -467,6 +538,12 @@ struct pmu_events_table { size_t length; }; +/* Struct used to make the PMU metric table implementation opaque to callers. */ +struct pmu_metrics_table { + const struct compact_pmu_event *entries; + size_t length; +}; + /* * 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. @@ -478,7 +555,8 @@ struct pmu_events_table { struct pmu_events_map { const char *arch; const char *cpuid; - struct pmu_events_table table; + struct pmu_events_table event_table; + struct pmu_metrics_table metric_table; }; /* @@ -492,9 +570,13 @@ const struct pmu_events_map pmu_events_map[] = { _args.output_file.write("""{ \t.arch = "testarch", \t.cpuid = "testcpu", -\t.table = { -\t.entries = pme_test_soc_cpu, -\t.length = ARRAY_SIZE(pme_test_soc_cpu), +\t.event_table = { +\t\t.entries = pmu_events__test_soc_cpu, +\t\t.length = ARRAY_SIZE(pmu_events__test_soc_cpu), +\t}, +\t.metric_table = { +\t\t.entries = pmu_metrics__test_soc_cpu, +\t\t.length = ARRAY_SIZE(pmu_metrics__test_soc_cpu), \t} }, """) @@ -505,14 +587,31 @@ const struct pmu_events_map pmu_events_map[] = { for row in table: # Skip the first row or any row beginning with #. if not first and len(row) > 0 and not row[0].startswith('#'): - tblname = file_name_to_table_name([], row[2].replace('/', '_')) + event_tblname = file_name_to_table_name('pmu_events_', [], row[2].replace('/', '_')) + if event_tblname in _event_tables: + event_size = f'ARRAY_SIZE({event_tblname})' + else: + event_tblname = 'NULL' + event_size = '0' + metric_tblname = file_name_to_table_name('pmu_metrics_', [], row[2].replace('/', '_')) + if metric_tblname in _metric_tables: + metric_size = f'ARRAY_SIZE({metric_tblname})' + else: + metric_tblname = 'NULL' + metric_size = '0' + if event_size == '0' and metric_size == '0': + continue cpuid = row[0].replace('\\', '\\\\') _args.output_file.write(f"""{{ \t.arch = "{arch}", \t.cpuid = "{cpuid}", -\t.table = {{ -\t\t.entries = {tblname}, -\t\t.length = ARRAY_SIZE({tblname}) +\t.event_table = {{ +\t\t.entries = {event_tblname}, +\t\t.length = {event_size} +\t}}, +\t.metric_table = {{ +\t\t.entries = {metric_tblname}, +\t\t.length = {metric_size} \t}} }}, """) @@ -521,7 +620,8 @@ const struct pmu_events_map pmu_events_map[] = { _args.output_file.write("""{ \t.arch = 0, \t.cpuid = 0, -\t.table = { 0, 0 }, +\t.event_table = { 0, 0 }, +\t.metric_table = { 0, 0 }, } }; """) @@ -532,14 +632,36 @@ def print_system_mapping_table() -> None: _args.output_file.write(""" struct pmu_sys_events { \tconst char *name; -\tstruct pmu_events_table table; +\tstruct pmu_events_table event_table; +\tstruct pmu_metrics_table metric_table; }; static const struct pmu_sys_events pmu_sys_event_tables[] = { """) + printed_metric_tables = [] for tblname in _sys_event_tables: _args.output_file.write(f"""\t{{ -\t\t.table = {{ +\t\t.event_table = {{ +\t\t\t.entries = {tblname}, +\t\t\t.length = ARRAY_SIZE({tblname}) +\t\t}},""") + metric_tblname = _sys_event_table_to_metric_table_mapping[tblname] + if metric_tblname in _sys_metric_tables: + _args.output_file.write(f""" +\t\t.metric_table = {{ +\t\t\t.entries = {metric_tblname}, +\t\t\t.length = ARRAY_SIZE({metric_tblname}) +\t\t}},""") + printed_metric_tables.append(metric_tblname) + _args.output_file.write(f""" +\t\t.name = \"{tblname}\", +\t}}, +""") + for tblname in _sys_metric_tables: + if tblname in printed_metric_tables: + continue + _args.output_file.write(f"""\t{{ +\t\t.metric_table = {{ \t\t\t.entries = {tblname}, \t\t\t.length = ARRAY_SIZE({tblname}) \t\t}}, @@ -547,11 +669,12 @@ static const struct pmu_sys_events pmu_sys_event_tables[] = { \t}}, """) _args.output_file.write("""\t{ -\t\t.table = { 0, 0 } +\t\t.event_table = { 0, 0 }, +\t\t.metric_table = { 0, 0 }, \t}, }; -static void decompress(int offset, struct pmu_event *pe) +static void decompress_event(int offset, struct pmu_event *pe) { \tconst char *p = &big_c_string[offset]; """) @@ -564,6 +687,19 @@ static void decompress(int offset, struct pmu_event *pe) _args.output_file.write('\twhile (*p++);') _args.output_file.write("""} +static void decompress_metric(int offset, struct pmu_metric *pm) +{ +\tconst char *p = &big_c_string[offset]; +""") + for attr in _json_metric_attributes: + _args.output_file.write(f""" +\tpm->{attr} = (*p == '\\0' ? NULL : p); +""") + if attr == _json_metric_attributes[-1]: + continue + _args.output_file.write('\twhile (*p++);') + _args.output_file.write("""} + int pmu_events_table_for_each_event(const struct pmu_events_table *table, pmu_event_iter_fn fn, void *data) @@ -572,7 +708,9 @@ int pmu_events_table_for_each_event(const struct pmu_events_table *table, struct pmu_event pe; int ret; - decompress(table->entries[i].offset, &pe); + decompress_event(table->entries[i].offset, &pe); + if (!pe.name) + continue; ret = fn(&pe, table, data); if (ret) return ret; @@ -580,7 +718,25 @@ int pmu_events_table_for_each_event(const struct pmu_events_table *table, return 0; } -const struct pmu_events_table *perf_pmu__find_table(struct perf_pmu *pmu) +int pmu_metrics_table_for_each_metric(const struct pmu_metrics_table *table, + pmu_metric_iter_fn fn, + void *data) +{ + for (size_t i = 0; i < table->length; i++) { + struct pmu_metric pm; + int ret; + + decompress_metric(table->entries[i].offset, &pm); + if (!pm.metric_expr) + continue; + ret = fn(&pm, table, data); + if (ret) + return ret; + } + return 0; +} + +const struct pmu_events_table *perf_pmu__find_events_table(struct perf_pmu *pmu) { const struct pmu_events_table *table = NULL; char *cpuid = perf_pmu__getcpuid(pmu); @@ -599,7 +755,34 @@ const struct pmu_events_table *perf_pmu__find_table(struct perf_pmu *pmu) break; if (!strcmp_cpuid_str(map->cpuid, cpuid)) { - table = &map->table; + table = &map->event_table; + break; + } + } + free(cpuid); + return table; +} + +const struct pmu_metrics_table *perf_pmu__find_metrics_table(struct perf_pmu *pmu) +{ + const struct pmu_metrics_table *table = NULL; + char *cpuid = perf_pmu__getcpuid(pmu); + int i; + + /* on some platforms which uses cpus map, cpuid can be NULL for + * PMUs other than CORE PMUs. + */ + if (!cpuid) + return NULL; + + i = 0; + for (;;) { + const struct pmu_events_map *map = &pmu_events_map[i++]; + if (!map->arch) + break; + + if (!strcmp_cpuid_str(map->cpuid, cpuid)) { + table = &map->metric_table; break; } } @@ -613,7 +796,18 @@ const struct pmu_events_table *find_core_events_table(const char *arch, const ch tables->arch; tables++) { if (!strcmp(tables->arch, arch) && !strcmp_cpuid_str(tables->cpuid, cpuid)) - return &tables->table; + return &tables->event_table; + } + return NULL; +} + +const struct pmu_metrics_table *find_core_metrics_table(const char *arch, const char *cpuid) +{ + for (const struct pmu_events_map *tables = &pmu_events_map[0]; + tables->arch; + tables++) { + if (!strcmp(tables->arch, arch) && !strcmp_cpuid_str(tables->cpuid, cpuid)) + return &tables->metric_table; } return NULL; } @@ -623,7 +817,20 @@ int pmu_for_each_core_event(pmu_event_iter_fn fn, void *data) for (const struct pmu_events_map *tables = &pmu_events_map[0]; tables->arch; tables++) { - int ret = pmu_events_table_for_each_event(&tables->table, fn, data); + int ret = pmu_events_table_for_each_event(&tables->event_table, fn, data); + + if (ret) + return ret; + } + return 0; +} + +int pmu_for_each_core_metric(pmu_metric_iter_fn fn, void *data) +{ + for (const struct pmu_events_map *tables = &pmu_events_map[0]; + tables->arch; + tables++) { + int ret = pmu_metrics_table_for_each_metric(&tables->metric_table, fn, data); if (ret) return ret; @@ -637,7 +844,7 @@ const struct pmu_events_table *find_sys_events_table(const char *name) tables->name; tables++) { if (!strcmp(tables->name, name)) - return &tables->table; + return &tables->event_table; } return NULL; } @@ -647,7 +854,20 @@ int pmu_for_each_sys_event(pmu_event_iter_fn fn, void *data) for (const struct pmu_sys_events *tables = &pmu_sys_event_tables[0]; tables->name; tables++) { - int ret = pmu_events_table_for_each_event(&tables->table, fn, data); + int ret = pmu_events_table_for_each_event(&tables->event_table, fn, data); + + if (ret) + return ret; + } + return 0; +} + +int pmu_for_each_sys_metric(pmu_metric_iter_fn fn, void *data) +{ + for (const struct pmu_sys_events *tables = &pmu_sys_event_tables[0]; + tables->name; + tables++) { + int ret = pmu_metrics_table_for_each_metric(&tables->metric_table, fn, data); if (ret) return ret; @@ -670,12 +890,24 @@ def main() -> None: action: Callable[[Sequence[str], os.DirEntry], None]) -> None: """Replicate the directory/file walking behavior of C's file tree walk.""" for item in os.scandir(path): + if _args.model != 'all' and item.is_dir(): + # Check if the model matches one in _args.model. + if len(parents) == _args.model.split(',')[0].count('/'): + # We're testing the correct directory. + item_path = '/'.join(parents) + ('/' if len(parents) > 0 else '') + item.name + if 'test' not in item_path and item_path not in _args.model.split(','): + continue action(parents, item) if item.is_dir(): ftw(item.path, parents + [item.name], action) ap = argparse.ArgumentParser() ap.add_argument('arch', help='Architecture name like x86') + ap.add_argument('model', help='''Select a model such as skylake to +reduce the code size. Normally set to "all". For architectures like +ARM64 with an implementor/model, the model must include the implementor +such as "arm/cortex-a34".''', + default='all') ap.add_argument( 'starting_dir', type=dir_path, @@ -721,7 +953,8 @@ struct compact_pmu_event { for arch in archs: arch_path = f'{_args.starting_dir}/{arch}' ftw(arch_path, [], process_one_file) - print_events_table_suffix() + print_pending_events() + print_pending_metrics() print_mapping_table(archs) print_system_mapping_table() diff --git a/tools/perf/pmu-events/metric.py b/tools/perf/pmu-events/metric.py index 4797ed4fd817..77ea6ff98538 100644 --- a/tools/perf/pmu-events/metric.py +++ b/tools/perf/pmu-events/metric.py @@ -4,7 +4,7 @@ import ast import decimal import json import re -from typing import Dict, List, Optional, Set, Union +from typing import Dict, List, Optional, Set, Tuple, Union class Expression: @@ -26,6 +26,9 @@ class Expression: """Returns true when two expressions are the same.""" raise NotImplementedError() + def Substitute(self, name: str, expression: 'Expression') -> 'Expression': + raise NotImplementedError() + def __str__(self) -> str: return self.ToPerfJson() @@ -186,6 +189,15 @@ class Operator(Expression): other.lhs) and self.rhs.Equals(other.rhs) return False + def Substitute(self, name: str, expression: Expression) -> Expression: + if self.Equals(expression): + return Event(name) + lhs = self.lhs.Substitute(name, expression) + rhs = None + if self.rhs: + rhs = self.rhs.Substitute(name, expression) + return Operator(self.operator, lhs, rhs) + class Select(Expression): """Represents a select ternary in the parse tree.""" @@ -225,6 +237,14 @@ class Select(Expression): other.false_val) and self.true_val.Equals(other.true_val) return False + def Substitute(self, name: str, expression: Expression) -> Expression: + if self.Equals(expression): + return Event(name) + true_val = self.true_val.Substitute(name, expression) + cond = self.cond.Substitute(name, expression) + false_val = self.false_val.Substitute(name, expression) + return Select(true_val, cond, false_val) + class Function(Expression): """A function in an expression like min, max, d_ratio.""" @@ -261,10 +281,21 @@ class Function(Expression): def Equals(self, other: Expression) -> bool: if isinstance(other, Function): - return self.fn == other.fn and self.lhs.Equals( - other.lhs) and self.rhs.Equals(other.rhs) + result = self.fn == other.fn and self.lhs.Equals(other.lhs) + if self.rhs: + result = result and self.rhs.Equals(other.rhs) + return result return False + def Substitute(self, name: str, expression: Expression) -> Expression: + if self.Equals(expression): + return Event(name) + lhs = self.lhs.Substitute(name, expression) + rhs = None + if self.rhs: + rhs = self.rhs.Substitute(name, expression) + return Function(self.fn, lhs, rhs) + def _FixEscapes(s: str) -> str: s = re.sub(r'([^\\]),', r'\1\\,', s) @@ -291,6 +322,9 @@ class Event(Expression): def Equals(self, other: Expression) -> bool: return isinstance(other, Event) and self.name == other.name + def Substitute(self, name: str, expression: Expression) -> Expression: + return self + class Constant(Expression): """A constant within the expression tree.""" @@ -315,6 +349,9 @@ class Constant(Expression): def Equals(self, other: Expression) -> bool: return isinstance(other, Constant) and self.value == other.value + def Substitute(self, name: str, expression: Expression) -> Expression: + return self + class Literal(Expression): """A runtime literal within the expression tree.""" @@ -334,6 +371,9 @@ class Literal(Expression): def Equals(self, other: Expression) -> bool: return isinstance(other, Literal) and self.value == other.value + def Substitute(self, name: str, expression: Expression) -> Expression: + return self + def min(lhs: Union[int, float, Expression], rhs: Union[int, float, Expression]) -> Function: @@ -459,6 +499,7 @@ class MetricGroup: class _RewriteIfExpToSelect(ast.NodeTransformer): + """Transformer to convert if-else nodes to Select expressions.""" def visit_IfExp(self, node): # pylint: disable=invalid-name @@ -496,7 +537,37 @@ def ParsePerfJson(orig: str) -> Expression: for kw in keywords: py = re.sub(rf'Event\(r"{kw}"\)', kw, py) - parsed = ast.parse(py, mode='eval') + try: + parsed = ast.parse(py, mode='eval') + except SyntaxError as e: + raise SyntaxError(f'Parsing expression:\n{orig}') from e _RewriteIfExpToSelect().visit(parsed) parsed = ast.fix_missing_locations(parsed) return _Constify(eval(compile(parsed, orig, 'eval'))) + + +def RewriteMetricsInTermsOfOthers(metrics: List[Tuple[str, Expression]] + )-> Dict[str, Expression]: + """Shorten metrics by rewriting in terms of others. + + Args: + metrics (list): pairs of metric names and their expressions. + Returns: + Dict: mapping from a metric name to a shortened expression. + """ + updates: Dict[str, Expression] = dict() + for outer_name, outer_expression in metrics: + updated = outer_expression + while True: + for inner_name, inner_expression in metrics: + if inner_name.lower() == outer_name.lower(): + continue + if inner_name in updates: + inner_expression = updates[inner_name] + updated = updated.Substitute(inner_name, inner_expression) + if updated.Equals(outer_expression): + break + if outer_name in updates and updated.Equals(updates[outer_name]): + break + updates[outer_name] = updated + return updates diff --git a/tools/perf/pmu-events/metric_test.py b/tools/perf/pmu-events/metric_test.py index 15315d0f716c..40a3c7d8b2bc 100644..100755 --- a/tools/perf/pmu-events/metric_test.py +++ b/tools/perf/pmu-events/metric_test.py @@ -1,8 +1,11 @@ +#!/usr/bin/env python3 # SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) import unittest from metric import Constant from metric import Event +from metric import Expression from metric import ParsePerfJson +from metric import RewriteMetricsInTermsOfOthers class TestMetricExpressions(unittest.TestCase): @@ -87,8 +90,8 @@ class TestMetricExpressions(unittest.TestCase): after = r'min((a + b if c > 1 else c + d), e + f)' self.assertEqual(ParsePerfJson(before).ToPerfJson(), after) - before =3D r'a if b else c if d else e' - after =3D r'(a if b else (c if d else e))' + before = r'a if b else c if d else e' + after = r'(a if b else (c if d else e))' self.assertEqual(ParsePerfJson(before).ToPerfJson(), after) def test_ToPython(self): @@ -153,5 +156,13 @@ class TestMetricExpressions(unittest.TestCase): after = '0 * SLOTS' self.assertEqual(ParsePerfJson(before).Simplify().ToPerfJson(), after) + def test_RewriteMetricsInTermsOfOthers(self): + Expression.__eq__ = lambda e1, e2: e1.Equals(e2) + before = [('m1', ParsePerfJson('a + b + c + d')), + ('m2', ParsePerfJson('a + b + c'))] + after = {'m1': ParsePerfJson('m2 + d')} + self.assertEqual(RewriteMetricsInTermsOfOthers(before), after) + Expression.__eq__ = None + if __name__ == '__main__': unittest.main() diff --git a/tools/perf/pmu-events/pmu-events.h b/tools/perf/pmu-events/pmu-events.h index fe343c4d8016..b7d4a66b8ad2 100644 --- a/tools/perf/pmu-events/pmu-events.h +++ b/tools/perf/pmu-events/pmu-events.h @@ -23,27 +23,47 @@ struct pmu_event { const char *unit; const char *perpkg; const char *aggr_mode; - const char *metric_expr; + const char *deprecated; +}; + +struct pmu_metric { const char *metric_name; const char *metric_group; - const char *deprecated; + const char *metric_expr; + const char *unit; + const char *compat; + const char *aggr_mode; const char *metric_constraint; + const char *desc; + const char *long_desc; }; struct pmu_events_table; +struct pmu_metrics_table; typedef int (*pmu_event_iter_fn)(const struct pmu_event *pe, const struct pmu_events_table *table, void *data); +typedef int (*pmu_metric_iter_fn)(const struct pmu_metric *pm, + const struct pmu_metrics_table *table, + void *data); + int pmu_events_table_for_each_event(const struct pmu_events_table *table, pmu_event_iter_fn fn, void *data); +int pmu_metrics_table_for_each_metric(const struct pmu_metrics_table *table, pmu_metric_iter_fn fn, + void *data); -const struct pmu_events_table *perf_pmu__find_table(struct perf_pmu *pmu); +const struct pmu_events_table *perf_pmu__find_events_table(struct perf_pmu *pmu); +const struct pmu_metrics_table *perf_pmu__find_metrics_table(struct perf_pmu *pmu); const struct pmu_events_table *find_core_events_table(const char *arch, const char *cpuid); +const struct pmu_metrics_table *find_core_metrics_table(const char *arch, const char *cpuid); int pmu_for_each_core_event(pmu_event_iter_fn fn, void *data); +int pmu_for_each_core_metric(pmu_metric_iter_fn fn, void *data); const struct pmu_events_table *find_sys_events_table(const char *name); +const struct pmu_metrics_table *find_sys_metrics_table(const char *name); int pmu_for_each_sys_event(pmu_event_iter_fn fn, void *data); +int pmu_for_each_sys_metric(pmu_metric_iter_fn fn, void *data); #endif diff --git a/tools/perf/scripts/python/flamegraph.py b/tools/perf/scripts/python/flamegraph.py index b6af1dd5f816..cf7ce8229a6c 100755 --- a/tools/perf/scripts/python/flamegraph.py +++ b/tools/perf/scripts/python/flamegraph.py @@ -19,12 +19,34 @@ # pylint: disable=missing-function-docstring from __future__ import print_function -import sys -import os -import io import argparse +import hashlib +import io import json +import os import subprocess +import sys +import urllib.request + +minimal_html = """<head> + <link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/npm/d3-flame-graph@4.1.3/dist/d3-flamegraph.css"> +</head> +<body> + <div id="chart"></div> + <script type="text/javascript" src="https://d3js.org/d3.v7.js"></script> + <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/d3-flame-graph@4.1.3/dist/d3-flamegraph.min.js"></script> + <script type="text/javascript"> + const stacks = [/** @flamegraph_json **/]; + // Note, options is unused. + const options = [/** @options_json **/]; + + var chart = flamegraph(); + d3.select("#chart") + .datum(stacks[0]) + .call(chart); + </script> +</body> +""" # pylint: disable=too-few-public-methods class Node: @@ -50,16 +72,6 @@ class FlameGraphCLI: self.args = args self.stack = Node("all", "root") - if self.args.format == "html" and \ - not os.path.isfile(self.args.template): - print("Flame Graph template {} does not exist. Please install " - "the js-d3-flame-graph (RPM) or libjs-d3-flame-graph (deb) " - "package, specify an existing flame graph template " - "(--template PATH) or another output format " - "(--format FORMAT).".format(self.args.template), - file=sys.stderr) - sys.exit(1) - @staticmethod def get_libtype_from_dso(dso): """ @@ -128,16 +140,63 @@ class FlameGraphCLI: } options_json = json.dumps(options) + template_md5sum = None + if self.args.format == "html": + if os.path.isfile(self.args.template): + template = f"file://{self.args.template}" + else: + if not self.args.allow_download: + print(f"""Warning: Flame Graph template '{self.args.template}' +does not exist. To avoid this please install a package such as the +js-d3-flame-graph or libjs-d3-flame-graph, specify an existing flame +graph template (--template PATH) or use another output format (--format +FORMAT).""", + file=sys.stderr) + if self.args.input == "-": + print("""Not attempting to download Flame Graph template as script command line +input is disabled due to using live mode. If you want to download the +template retry without live mode. For example, use 'perf record -a -g +-F 99 sleep 60' and 'perf script report flamegraph'. Alternatively, +download the template from: +https://cdn.jsdelivr.net/npm/d3-flame-graph@4.1.3/dist/templates/d3-flamegraph-base.html +and place it at: +/usr/share/d3-flame-graph/d3-flamegraph-base.html""", + file=sys.stderr) + quit() + s = None + while s != "y" and s != "n": + s = input("Do you wish to download a template from cdn.jsdelivr.net? (this warning can be suppressed with --allow-download) [yn] ").lower() + if s == "n": + quit() + template = "https://cdn.jsdelivr.net/npm/d3-flame-graph@4.1.3/dist/templates/d3-flamegraph-base.html" + template_md5sum = "143e0d06ba69b8370b9848dcd6ae3f36" + try: - with io.open(self.args.template, encoding="utf-8") as template: - output_str = ( - template.read() - .replace("/** @options_json **/", options_json) - .replace("/** @flamegraph_json **/", stacks_json) - ) - except IOError as err: - print("Error reading template file: {}".format(err), file=sys.stderr) - sys.exit(1) + with urllib.request.urlopen(template) as template: + output_str = "".join([ + l.decode("utf-8") for l in template.readlines() + ]) + except Exception as err: + print(f"Error reading template {template}: {err}\n" + "a minimal flame graph will be generated", file=sys.stderr) + output_str = minimal_html + template_md5sum = None + + if template_md5sum: + download_md5sum = hashlib.md5(output_str.encode("utf-8")).hexdigest() + if download_md5sum != template_md5sum: + s = None + while s != "y" and s != "n": + s = input(f"""Unexpected template md5sum. +{download_md5sum} != {template_md5sum}, for: +{output_str} +continue?[yn] """).lower() + if s == "n": + quit() + + output_str = output_str.replace("/** @options_json **/", options_json) + output_str = output_str.replace("/** @flamegraph_json **/", stacks_json) + output_fn = self.args.output or "flamegraph.html" else: output_str = stacks_json @@ -172,6 +231,10 @@ if __name__ == "__main__": choices=["blue-green", "orange"]) parser.add_argument("-i", "--input", help=argparse.SUPPRESS) + parser.add_argument("--allow-download", + default=False, + action="store_true", + help="allow unprompted downloading of HTML template") cli_args = parser.parse_args() cli = FlameGraphCLI(cli_args) diff --git a/tools/perf/tests/Build b/tools/perf/tests/Build index 90fd1eb317bb..fb9ac5dc4079 100644 --- a/tools/perf/tests/Build +++ b/tools/perf/tests/Build @@ -68,6 +68,7 @@ perf-y += perf-time-to-tsc.o perf-y += dlfilter-test.o perf-y += sigtrap.o perf-y += event_groups.o +perf-y += symbols.o $(OUTPUT)tests/llvm-src-base.c: tests/bpf-script-example.c tests/Build $(call rule_mkdir) diff --git a/tools/perf/tests/bpf-script-example.c b/tools/perf/tests/bpf-script-example.c index 7981c69ed1b4..b638cc99d5ae 100644 --- a/tools/perf/tests/bpf-script-example.c +++ b/tools/perf/tests/bpf-script-example.c @@ -43,7 +43,7 @@ struct { __type(value, int); } flip_table SEC(".maps"); -SEC("func=do_epoll_wait") +SEC("syscalls:sys_enter_epoll_pwait") int bpf_func__SyS_epoll_pwait(void *ctx) { int ind =0; diff --git a/tools/perf/tests/bpf.c b/tools/perf/tests/bpf.c index 17c023823713..ae9223f27cfb 100644 --- a/tools/perf/tests/bpf.c +++ b/tools/perf/tests/bpf.c @@ -23,7 +23,7 @@ #define NR_ITERS 111 #define PERF_TEST_BPF_PATH "/sys/fs/bpf/perf_test" -#ifdef HAVE_LIBBPF_SUPPORT +#if defined(HAVE_LIBBPF_SUPPORT) && defined(HAVE_LIBTRACEEVENT) #include <linux/bpf.h> #include <bpf/bpf.h> @@ -126,6 +126,10 @@ static int do_test(struct bpf_object *obj, int (*func)(void), err = parse_events_load_bpf_obj(&parse_state, &parse_state.list, obj, NULL); parse_events_error__exit(&parse_error); + if (err == -ENODATA) { + pr_debug("Failed to add events selected by BPF, debuginfo package not installed\n"); + return TEST_SKIP; + } if (err || list_empty(&parse_state.list)) { pr_debug("Failed to add events selected by BPF\n"); return TEST_FAIL; @@ -330,10 +334,10 @@ static int test__bpf(int i) static int test__basic_bpf_test(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { -#ifdef HAVE_LIBBPF_SUPPORT +#if defined(HAVE_LIBBPF_SUPPORT) && defined(HAVE_LIBTRACEEVENT) return test__bpf(0); #else - pr_debug("Skip BPF test because BPF support is not compiled\n"); + pr_debug("Skip BPF test because BPF or libtraceevent support is not compiled\n"); return TEST_SKIP; #endif } @@ -341,10 +345,10 @@ static int test__basic_bpf_test(struct test_suite *test __maybe_unused, static int test__bpf_pinning(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { -#ifdef HAVE_LIBBPF_SUPPORT +#if defined(HAVE_LIBBPF_SUPPORT) && defined(HAVE_LIBTRACEEVENT) return test__bpf(1); #else - pr_debug("Skip BPF test because BPF support is not compiled\n"); + pr_debug("Skip BPF test because BPF or libtraceevent support is not compiled\n"); return TEST_SKIP; #endif } @@ -352,30 +356,30 @@ static int test__bpf_pinning(struct test_suite *test __maybe_unused, static int test__bpf_prologue_test(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { -#if defined(HAVE_LIBBPF_SUPPORT) && defined(HAVE_BPF_PROLOGUE) +#if defined(HAVE_LIBBPF_SUPPORT) && defined(HAVE_BPF_PROLOGUE) && defined(HAVE_LIBTRACEEVENT) return test__bpf(2); #else - pr_debug("Skip BPF test because BPF support is not compiled\n"); + pr_debug("Skip BPF test because BPF or libtraceevent support is not compiled\n"); return TEST_SKIP; #endif } static struct test_case bpf_tests[] = { -#ifdef HAVE_LIBBPF_SUPPORT +#if defined(HAVE_LIBBPF_SUPPORT) && defined(HAVE_LIBTRACEEVENT) TEST_CASE("Basic BPF filtering", basic_bpf_test), TEST_CASE_REASON("BPF pinning", bpf_pinning, "clang isn't installed or environment missing BPF support"), #ifdef HAVE_BPF_PROLOGUE TEST_CASE_REASON("BPF prologue generation", bpf_prologue_test, - "clang isn't installed or environment missing BPF support"), + "clang/debuginfo isn't installed or environment missing BPF support"), #else TEST_CASE_REASON("BPF prologue generation", bpf_prologue_test, "not compiled in"), #endif #else - TEST_CASE_REASON("Basic BPF filtering", basic_bpf_test, "not compiled in"), - TEST_CASE_REASON("BPF pinning", bpf_pinning, "not compiled in"), - TEST_CASE_REASON("BPF prologue generation", bpf_prologue_test, "not compiled in"), + TEST_CASE_REASON("Basic BPF filtering", basic_bpf_test, "not compiled in or missing libtraceevent support"), + TEST_CASE_REASON("BPF pinning", bpf_pinning, "not compiled in or missing libtraceevent support"), + TEST_CASE_REASON("BPF prologue generation", bpf_prologue_test, "not compiled in or missing libtraceevent support"), #endif { .name = NULL, } }; diff --git a/tools/perf/tests/builtin-test.c b/tools/perf/tests/builtin-test.c index cfa61493c750..35cc3807cc9e 100644 --- a/tools/perf/tests/builtin-test.c +++ b/tools/perf/tests/builtin-test.c @@ -31,6 +31,7 @@ #include "builtin-test-list.h" static bool dont_fork; +const char *dso_to_test; struct test_suite *__weak arch_tests[] = { NULL, @@ -117,6 +118,7 @@ static struct test_suite *generic_tests[] = { &suite__dlfilter, &suite__sigtrap, &suite__event_groups, + &suite__symbols, NULL, }; @@ -521,6 +523,7 @@ int cmd_test(int argc, const char **argv) OPT_BOOLEAN('F', "dont-fork", &dont_fork, "Do not fork for testcase"), OPT_STRING('w', "workload", &workload, "work", "workload to run for testing"), + OPT_STRING(0, "dso", &dso_to_test, "dso", "dso to test"), OPT_END() }; const char * const test_subcommands[] = { "list", NULL }; diff --git a/tools/perf/tests/dwarf-unwind.c b/tools/perf/tests/dwarf-unwind.c index afdca7f2959f..ee983b677a6a 100644 --- a/tools/perf/tests/dwarf-unwind.c +++ b/tools/perf/tests/dwarf-unwind.c @@ -67,6 +67,7 @@ int test_dwarf_unwind__compare(void *p1, void *p2); int test_dwarf_unwind__krava_3(struct thread *thread); int test_dwarf_unwind__krava_2(struct thread *thread); int test_dwarf_unwind__krava_1(struct thread *thread); +int test__dwarf_unwind(struct test_suite *test, int subtest); #define MAX_STACK 8 @@ -195,8 +196,8 @@ NO_TAIL_CALL_ATTRIBUTE noinline int test_dwarf_unwind__krava_1(struct thread *th return ret; } -static int test__dwarf_unwind(struct test_suite *test __maybe_unused, - int subtest __maybe_unused) +noinline int test__dwarf_unwind(struct test_suite *test __maybe_unused, + int subtest __maybe_unused) { struct machine *machine; struct thread *thread; diff --git a/tools/perf/tests/expand-cgroup.c b/tools/perf/tests/expand-cgroup.c index 51fb5f34c1dd..672a27f37060 100644 --- a/tools/perf/tests/expand-cgroup.c +++ b/tools/perf/tests/expand-cgroup.c @@ -180,13 +180,13 @@ static int expand_metric_events(void) struct evlist *evlist; struct rblist metric_events; const char metric_str[] = "CPI"; - const struct pmu_events_table *pme_test; + const struct pmu_metrics_table *pme_test; evlist = evlist__new(); TEST_ASSERT_VAL("failed to get evlist", evlist); rblist__init(&metric_events); - pme_test = find_core_events_table("testarch", "testcpu"); + pme_test = find_core_metrics_table("testarch", "testcpu"); ret = metricgroup__parse_groups_test(evlist, pme_test, metric_str, false, false, &metric_events); if (ret < 0) { diff --git a/tools/perf/tests/parse-metric.c b/tools/perf/tests/parse-metric.c index 21b7ac00d798..9fec6040950c 100644 --- a/tools/perf/tests/parse-metric.c +++ b/tools/perf/tests/parse-metric.c @@ -72,7 +72,7 @@ static int __compute_metric(const char *name, struct value *vals, struct rblist metric_events = { .nr_entries = 0, }; - const struct pmu_events_table *pme_test; + const struct pmu_metrics_table *pme_test; struct perf_cpu_map *cpus; struct runtime_stat st; struct evlist *evlist; @@ -96,7 +96,7 @@ static int __compute_metric(const char *name, struct value *vals, runtime_stat__init(&st); /* Parse the metric into metric_events list. */ - pme_test = find_core_events_table("testarch", "testcpu"); + pme_test = find_core_metrics_table("testarch", "testcpu"); err = metricgroup__parse_groups_test(evlist, pme_test, name, false, false, &metric_events); diff --git a/tools/perf/tests/pmu-events.c b/tools/perf/tests/pmu-events.c index a9f2330f6257..accf44b3d968 100644 --- a/tools/perf/tests/pmu-events.c +++ b/tools/perf/tests/pmu-events.c @@ -337,36 +337,12 @@ static int compare_pmu_events(const struct pmu_event *e1, const struct pmu_event return -1; } - if (!is_same(e1->metric_expr, e2->metric_expr)) { - pr_debug2("testing event e1 %s: mismatched metric_expr, %s vs %s\n", - e1->name, e1->metric_expr, e2->metric_expr); - return -1; - } - - if (!is_same(e1->metric_name, e2->metric_name)) { - pr_debug2("testing event e1 %s: mismatched metric_name, %s vs %s\n", - e1->name, e1->metric_name, e2->metric_name); - return -1; - } - - if (!is_same(e1->metric_group, e2->metric_group)) { - pr_debug2("testing event e1 %s: mismatched metric_group, %s vs %s\n", - e1->name, e1->metric_group, e2->metric_group); - return -1; - } - if (!is_same(e1->deprecated, e2->deprecated)) { pr_debug2("testing event e1 %s: mismatched deprecated, %s vs %s\n", e1->name, e1->deprecated, e2->deprecated); return -1; } - if (!is_same(e1->metric_constraint, e2->metric_constraint)) { - pr_debug2("testing event e1 %s: mismatched metric_constant, %s vs %s\n", - e1->name, e1->metric_constraint, e2->metric_constraint); - return -1; - } - return 0; } @@ -432,9 +408,6 @@ static int test__pmu_event_table_core_callback(const struct pmu_event *pe, struct perf_pmu_test_event const **test_event_table; bool found = false; - if (!pe->name) - return 0; - if (pe->pmu) test_event_table = &uncore_events[0]; else @@ -496,7 +469,8 @@ static int test__pmu_event_table_sys_callback(const struct pmu_event *pe, static int test__pmu_event_table(struct test_suite *test __maybe_unused, int subtest __maybe_unused) { - const struct pmu_events_table *sys_event_table = find_sys_events_table("pme_test_soc_sys"); + const struct pmu_events_table *sys_event_table = + find_sys_events_table("pmu_events__test_soc_sys"); const struct pmu_events_table *table = find_core_events_table("testarch", "testcpu"); int map_events = 0, expected_events, err; @@ -840,7 +814,8 @@ struct metric { struct metric_ref metric_ref; }; -static int test__parsing_callback(const struct pmu_event *pe, const struct pmu_events_table *table, +static int test__parsing_callback(const struct pmu_metric *pm, + const struct pmu_metrics_table *table, void *data) { int *failures = data; @@ -854,10 +829,10 @@ static int test__parsing_callback(const struct pmu_event *pe, const struct pmu_e }; int err = 0; - if (!pe->metric_expr) + if (!pm->metric_expr) return 0; - pr_debug("Found metric '%s'\n", pe->metric_name); + pr_debug("Found metric '%s'\n", pm->metric_name); (*failures)++; /* @@ -877,14 +852,14 @@ static int test__parsing_callback(const struct pmu_event *pe, const struct pmu_e perf_evlist__set_maps(&evlist->core, cpus, NULL); runtime_stat__init(&st); - err = metricgroup__parse_groups_test(evlist, table, pe->metric_name, + err = metricgroup__parse_groups_test(evlist, table, pm->metric_name, false, false, &metric_events); if (err) { - if (!strcmp(pe->metric_name, "M1") || !strcmp(pe->metric_name, "M2") || - !strcmp(pe->metric_name, "M3")) { + if (!strcmp(pm->metric_name, "M1") || !strcmp(pm->metric_name, "M2") || + !strcmp(pm->metric_name, "M3")) { (*failures)--; - pr_debug("Expected broken metric %s skipping\n", pe->metric_name); + pr_debug("Expected broken metric %s skipping\n", pm->metric_name); err = 0; } goto out_err; @@ -912,7 +887,7 @@ static int test__parsing_callback(const struct pmu_event *pe, const struct pmu_e struct metric_expr *mexp; list_for_each_entry (mexp, &me->head, nd) { - if (strcmp(mexp->metric_name, pe->metric_name)) + if (strcmp(mexp->metric_name, pm->metric_name)) continue; pr_debug("Result %f\n", test_generic_metric(mexp, 0, &st)); err = 0; @@ -921,11 +896,11 @@ static int test__parsing_callback(const struct pmu_event *pe, const struct pmu_e } } } - pr_debug("Didn't find parsed metric %s", pe->metric_name); + pr_debug("Didn't find parsed metric %s", pm->metric_name); err = 1; out_err: if (err) - pr_debug("Broken metric %s\n", pe->metric_name); + pr_debug("Broken metric %s\n", pm->metric_name); /* ... cleanup. */ metricgroup__rblist_exit(&metric_events); @@ -941,8 +916,8 @@ static int test__parsing(struct test_suite *test __maybe_unused, { int failures = 0; - pmu_for_each_core_event(test__parsing_callback, &failures); - pmu_for_each_sys_event(test__parsing_callback, &failures); + pmu_for_each_core_metric(test__parsing_callback, &failures); + pmu_for_each_sys_metric(test__parsing_callback, &failures); return failures == 0 ? TEST_OK : TEST_FAIL; } @@ -975,6 +950,7 @@ static int metric_parse_fake(const char *metric_name, const char *str) pr_debug("expr__ctx_new failed"); return TEST_FAIL; } + ctx->sctx.is_test = true; if (expr__find_ids(str, NULL, ctx) < 0) { pr_err("expr__find_ids failed\n"); return -1; @@ -1021,14 +997,11 @@ out: return ret; } -static int test__parsing_fake_callback(const struct pmu_event *pe, - const struct pmu_events_table *table __maybe_unused, +static int test__parsing_fake_callback(const struct pmu_metric *pm, + const struct pmu_metrics_table *table __maybe_unused, void *data __maybe_unused) { - if (!pe->metric_expr) - return 0; - - return metric_parse_fake(pe->metric_name, pe->metric_expr); + return metric_parse_fake(pm->metric_name, pm->metric_expr); } /* @@ -1047,11 +1020,11 @@ static int test__parsing_fake(struct test_suite *test __maybe_unused, return err; } - err = pmu_for_each_core_event(test__parsing_fake_callback, NULL); + err = pmu_for_each_core_metric(test__parsing_fake_callback, NULL); if (err) return err; - return pmu_for_each_sys_event(test__parsing_fake_callback, NULL); + return pmu_for_each_sys_metric(test__parsing_fake_callback, NULL); } static struct test_case pmu_events_tests[] = { diff --git a/tools/perf/tests/sample-parsing.c b/tools/perf/tests/sample-parsing.c index 927c7f0cc4cc..25a3f6cece50 100644 --- a/tools/perf/tests/sample-parsing.c +++ b/tools/perf/tests/sample-parsing.c @@ -37,7 +37,7 @@ * in branch_stack variable. */ #define BS_EXPECTED_BE 0xa000d00000000000 -#define BS_EXPECTED_LE 0xd5000000 +#define BS_EXPECTED_LE 0x1aa00000000 #define FLAG(s) s->branch_stack->entries[i].flags static bool samples_same(const struct perf_sample *s1, diff --git a/tools/perf/tests/shell/buildid.sh b/tools/perf/tests/shell/buildid.sh index aaf851108ca3..0ce22ea0a7f1 100755 --- a/tools/perf/tests/shell/buildid.sh +++ b/tools/perf/tests/shell/buildid.sh @@ -66,7 +66,9 @@ check() esac echo "build id: ${id}" - link=${build_id_dir}/.build-id/${id:0:2}/${id:2} + id_file=${id#??} + id_dir=${id%$id_file} + link=$build_id_dir/.build-id/$id_dir/$id_file echo "link: ${link}" if [ ! -h $link ]; then @@ -74,7 +76,7 @@ check() exit 1 fi - file=${build_id_dir}/.build-id/${id:0:2}/`readlink ${link}`/elf + file=${build_id_dir}/.build-id/$id_dir/`readlink ${link}`/elf echo "file: ${file}" # Check for file permission of original file @@ -130,20 +132,22 @@ test_record() { data=$(mktemp /tmp/perf.data.XXX) build_id_dir=$(mktemp -d /tmp/perf.debug.XXX) - log=$(mktemp /tmp/perf.log.XXX) + log_out=$(mktemp /tmp/perf.log.out.XXX) + log_err=$(mktemp /tmp/perf.log.err.XXX) perf="perf --buildid-dir ${build_id_dir}" echo "running: perf record $@" - ${perf} record --buildid-all -o ${data} $@ &> ${log} + ${perf} record --buildid-all -o ${data} $@ 1>${log_out} 2>${log_err} if [ $? -ne 0 ]; then echo "failed: record $@" - echo "see log: ${log}" + echo "see log: ${log_err}" exit 1 fi - check ${@: -1} + args="$*" + check ${args##* } - rm -f ${log} + rm -f ${log_out} ${log_err} rm -rf ${build_id_dir} rm -rf ${data} } diff --git a/tools/perf/tests/shell/lib/coresight.sh b/tools/perf/tests/shell/lib/coresight.sh index 45a1477256b6..6c3d34ec64d8 100644 --- a/tools/perf/tests/shell/lib/coresight.sh +++ b/tools/perf/tests/shell/lib/coresight.sh @@ -58,9 +58,9 @@ perf_dump_aux_verify() { # compiler may produce different code depending on the compiler and # optimization options, so this is rough just to see if we're # either missing almost all the data or all of it - ATOM_FX_NUM=`grep I_ATOM_F "$DUMP" | wc -l` - ASYNC_NUM=`grep I_ASYNC "$DUMP" | wc -l` - TRACE_INFO_NUM=`grep I_TRACE_INFO "$DUMP" | wc -l` + ATOM_FX_NUM=$(grep -c I_ATOM_F "$DUMP") + ASYNC_NUM=$(grep -c I_ASYNC "$DUMP") + TRACE_INFO_NUM=$(grep -c I_TRACE_INFO "$DUMP") rm -f "$DUMP" # Arguments provide minimums for a pass @@ -96,18 +96,18 @@ perf_dump_aux_tid_verify() { # The TID test tools will print a TID per stdout line that are being # tested - TIDS=`cat "$2"` + TIDS=$(cat "$2") # Scan the perf report to find the TIDs that are actually CID in hex # and build a list of the ones found - FOUND_TIDS=`perf report --stdio --dump -i "$1" | \ + FOUND_TIDS=$(perf report --stdio --dump -i "$1" | \ grep -o "CID=0x[0-9a-z]\+" | sed 's/CID=//g' | \ - uniq | sort | uniq` + uniq | sort | uniq) # No CID=xxx found - maybe your kernel is reporting these as # VMID=xxx so look there if test -z "$FOUND_TIDS"; then - FOUND_TIDS=`perf report --stdio --dump -i "$1" | \ + FOUND_TIDS=$(perf report --stdio --dump -i "$1" | \ grep -o "VMID=0x[0-9a-z]\+" | sed 's/VMID=//g' | \ - uniq | sort | uniq` + uniq | sort | uniq) fi # Iterate over the list of TIDs that the test says it has and find @@ -116,7 +116,7 @@ perf_dump_aux_tid_verify() { for TID2 in $TIDS; do FOUND="" for TIDHEX in $FOUND_TIDS; do - TID=`printf "%i" $TIDHEX` + TID=$(printf "%i" $TIDHEX) if test "$TID" -eq "$TID2"; then FOUND="y" break diff --git a/tools/perf/tests/shell/lib/perf_json_output_lint.py b/tools/perf/tests/shell/lib/perf_json_output_lint.py index d90f8d102eb9..97598d14e532 100644 --- a/tools/perf/tests/shell/lib/perf_json_output_lint.py +++ b/tools/perf/tests/shell/lib/perf_json_output_lint.py @@ -40,19 +40,6 @@ def is_counter_value(num): return isfloat(num) or num == '<not counted>' or num == '<not supported>' def check_json_output(expected_items): - if expected_items != -1: - for line in Lines: - if 'failed' not in line: - count = 0 - count = line.count(',') - if count != expected_items and count >= 1 and count <= 3 and 'metric-value' in line: - # Events that generate >1 metric may have isolated metric - # values and possibly other prefixes like interval, core and - # aggregate-number. - continue - if count != expected_items: - raise RuntimeError(f'wrong number of fields. counted {count} expected {expected_items}' - f' in \'{line}\'') checks = { 'aggregate-number': lambda x: isfloat(x), 'core': lambda x: True, @@ -73,6 +60,16 @@ def check_json_output(expected_items): } input = '[\n' + ','.join(Lines) + '\n]' for item in json.loads(input): + if expected_items != -1: + count = len(item) + if count != expected_items and count >= 1 and count <= 4 and 'metric-value' in item: + # Events that generate >1 metric may have isolated metric + # values and possibly other prefixes like interval, core and + # aggregate-number. + pass + elif count != expected_items: + raise RuntimeError(f'wrong number of fields. counted {count} expected {expected_items}' + f' in \'{item}\'') for key, value in item.items(): if key not in checks: raise RuntimeError(f'Unexpected key: key={key} value={value}') @@ -82,11 +79,11 @@ def check_json_output(expected_items): try: if args.no_args or args.system_wide or args.event: - expected_items = 6 - elif args.interval or args.per_thread or args.system_wide_no_aggr: expected_items = 7 - elif args.per_core or args.per_socket or args.per_node or args.per_die: + elif args.interval or args.per_thread or args.system_wide_no_aggr: expected_items = 8 + elif args.per_core or args.per_socket or args.per_node or args.per_die: + expected_items = 9 else: # If no option is specified, don't check the number of items. expected_items = -1 diff --git a/tools/perf/tests/shell/lib/probe_vfs_getname.sh b/tools/perf/tests/shell/lib/probe_vfs_getname.sh index ed0a3972c4c8..60c5e34f90c4 100644 --- a/tools/perf/tests/shell/lib/probe_vfs_getname.sh +++ b/tools/perf/tests/shell/lib/probe_vfs_getname.sh @@ -22,3 +22,11 @@ skip_if_no_debuginfo() { add_probe_vfs_getname -v 2>&1 | grep -E -q "^(Failed to find the path for the kernel|Debuginfo-analysis is not supported)|(file has no debug information)" && return 2 return 1 } + +# check if perf is compiled with libtraceevent support +skip_no_probe_record_support() { + if [ $had_vfs_getname -eq 1 ] ; then + perf record --dry-run -e $1 2>&1 | grep "libtraceevent is necessary for tracepoint support" && return 2 + return 1 + fi +} diff --git a/tools/perf/tests/shell/lock_contention.sh b/tools/perf/tests/shell/lock_contention.sh index b05f1b1ca6c8..be5fcafb26aa 100755 --- a/tools/perf/tests/shell/lock_contention.sh +++ b/tools/perf/tests/shell/lock_contention.sh @@ -128,7 +128,7 @@ test_type_filter() echo "Testing perf lock contention --type-filter (w/ spinlock)" perf lock contention -i ${perfdata} -Y spinlock -q 2> ${result} if [ $(grep -c -v spinlock "${result}") != "0" ]; then - echo "[Fail] Recorded should not have non-spinlocks:" $(cat "${result}") + echo "[Fail] Recorded result should not have non-spinlocks:" $(cat "${result}") err=1 exit fi @@ -139,7 +139,7 @@ test_type_filter() perf lock con -a -b -Y spinlock -q -- perf bench sched messaging > /dev/null 2> ${result} if [ $(grep -c -v spinlock "${result}") != "0" ]; then - echo "[Fail] Recorded should not have non-spinlocks:" $(cat "${result}") + echo "[Fail] BPF result should not have non-spinlocks:" $(cat "${result}") err=1 exit fi @@ -160,7 +160,7 @@ test_lock_filter() local type=$(head -1 "${result}" | awk '{ print $8 }' | sed -e 's/:.*//') if [ $(grep -c -v "${type}" "${result}") != "0" ]; then - echo "[Fail] Recorded should not have non-${type} locks:" $(cat "${result}") + echo "[Fail] Recorded result should not have non-${type} locks:" $(cat "${result}") err=1 exit fi @@ -171,7 +171,63 @@ test_lock_filter() perf lock con -a -b -L tasklist_lock -q -- perf bench sched messaging > /dev/null 2> ${result} if [ $(grep -c -v "${type}" "${result}") != "0" ]; then - echo "[Fail] Recorded should not have non-${type} locks:" $(cat "${result}") + echo "[Fail] BPF result should not have non-${type} locks:" $(cat "${result}") + err=1 + exit + fi +} + +test_stack_filter() +{ + echo "Testing perf lock contention --callstack-filter (w/ unix_stream)" + perf lock contention -i ${perfdata} -v -q 2> ${result} + if [ $(grep -c unix_stream "${result}") == "0" ]; then + echo "[Skip] Could not find 'unix_stream'" + return + fi + + perf lock contention -i ${perfdata} -E 1 -S unix_stream -q 2> ${result} + if [ $(cat "${result}" | wc -l) != "1" ]; then + echo "[Fail] Recorded result should have a lock from unix_stream:" $(cat "${result}") + err=1 + exit + fi + + if ! perf lock con -b true > /dev/null 2>&1 ; then + return + fi + + perf lock con -a -b -S unix_stream -E 1 -q -- perf bench sched messaging > /dev/null 2> ${result} + if [ $(cat "${result}" | wc -l) != "1" ]; then + echo "[Fail] BPF result should have a lock from unix_stream:" $(cat "${result}") + err=1 + exit + fi +} + +test_aggr_task_stack_filter() +{ + echo "Testing perf lock contention --callstack-filter with task aggregation" + perf lock contention -i ${perfdata} -v -q 2> ${result} + if [ $(grep -c unix_stream "${result}") == "0" ]; then + echo "[Skip] Could not find 'unix_stream'" + return + fi + + perf lock contention -i ${perfdata} -t -E 1 -S unix_stream -q 2> ${result} + if [ $(cat "${result}" | wc -l) != "1" ]; then + echo "[Fail] Recorded result should have a task from unix_stream:" $(cat "${result}") + err=1 + exit + fi + + if ! perf lock con -b true > /dev/null 2>&1 ; then + return + fi + + perf lock con -a -b -t -S unix_stream -E 1 -q -- perf bench sched messaging > /dev/null 2> ${result} + if [ $(cat "${result}" | wc -l) != "1" ]; then + echo "[Fail] BPF result should have a task from unix_stream:" $(cat "${result}") err=1 exit fi @@ -186,5 +242,7 @@ test_aggr_task test_aggr_addr test_type_filter test_lock_filter +test_stack_filter +test_aggr_task_stack_filter exit ${err} diff --git a/tools/perf/tests/shell/record+probe_libc_inet_pton.sh b/tools/perf/tests/shell/record+probe_libc_inet_pton.sh index 57e7a6a470c9..b4149b2db4c6 100755 --- a/tools/perf/tests/shell/record+probe_libc_inet_pton.sh +++ b/tools/perf/tests/shell/record+probe_libc_inet_pton.sh @@ -11,6 +11,7 @@ # Arnaldo Carvalho de Melo <acme@kernel.org>, 2017 . $(dirname $0)/lib/probe.sh +. $(dirname $0)/lib/probe_vfs_getname.sh libc=$(grep -w libc /proc/self/maps | head -1 | sed -r 's/.*[[:space:]](\/.*)/\1/g') nm -Dg $libc 2>/dev/null | fgrep -q inet_pton || exit 254 @@ -57,7 +58,17 @@ trace_libc_inet_pton_backtrace() { perf_data=`mktemp -u /tmp/perf.data.XXX` perf_script=`mktemp -u /tmp/perf.script.XXX` + + # Check presence of libtraceevent support to run perf record + skip_no_probe_record_support "$event_name/$eventattr/" + [ $? -eq 2 ] && return 2 + perf record -e $event_name/$eventattr/ -o $perf_data ping -6 -c 1 ::1 > /dev/null 2>&1 + # check if perf data file got created in above step. + if [ ! -e $perf_data ]; then + printf "FAIL: perf record failed to create \"%s\" \n" "$perf_data" + return 1 + fi perf script -i $perf_data | tac | grep -m1 ^ping -B9 | tac > $perf_script exec 3<$perf_script diff --git a/tools/perf/tests/shell/record+script_probe_vfs_getname.sh b/tools/perf/tests/shell/record+script_probe_vfs_getname.sh index 7f83b2715b9a..1341437e1bd9 100755 --- a/tools/perf/tests/shell/record+script_probe_vfs_getname.sh +++ b/tools/perf/tests/shell/record+script_probe_vfs_getname.sh @@ -17,6 +17,9 @@ skip_if_no_perf_probe || exit 2 record_open_file() { echo "Recording open file:" + # Check presence of libtraceevent support to run perf record + skip_no_probe_record_support "probe:vfs_getname*" + [ $? -eq 2 ] && return 2 perf record -o ${perfdata} -e probe:vfs_getname\* touch $file } diff --git a/tools/perf/tests/shell/stat+csv_output.sh b/tools/perf/tests/shell/stat+csv_output.sh index b7f050aa6210..324fc9e6edd7 100755 --- a/tools/perf/tests/shell/stat+csv_output.sh +++ b/tools/perf/tests/shell/stat+csv_output.sh @@ -7,6 +7,7 @@ set -e skip_test=0 +csv_sep=@ function commachecker() { @@ -34,7 +35,7 @@ function commachecker() [ "$x" = "Failed" ] && continue # Count the number of commas - x=$(echo $line | tr -d -c ',') + x=$(echo $line | tr -d -c $csv_sep) cnt="${#x}" # echo $line $cnt [[ ! "$cnt" =~ $exp ]] && { @@ -54,7 +55,7 @@ function ParanoidAndNotRoot() check_no_args() { echo -n "Checking CSV output: no args " - perf stat -x, true 2>&1 | commachecker --no-args + perf stat -x$csv_sep true 2>&1 | commachecker --no-args echo "[Success]" } @@ -66,7 +67,7 @@ check_system_wide() echo "[Skip] paranoid and not root" return fi - perf stat -x, -a true 2>&1 | commachecker --system-wide + perf stat -x$csv_sep -a true 2>&1 | commachecker --system-wide echo "[Success]" } @@ -79,14 +80,14 @@ check_system_wide_no_aggr() return fi echo -n "Checking CSV output: system wide no aggregation " - perf stat -x, -A -a --no-merge true 2>&1 | commachecker --system-wide-no-aggr + perf stat -x$csv_sep -A -a --no-merge true 2>&1 | commachecker --system-wide-no-aggr echo "[Success]" } check_interval() { echo -n "Checking CSV output: interval " - perf stat -x, -I 1000 true 2>&1 | commachecker --interval + perf stat -x$csv_sep -I 1000 true 2>&1 | commachecker --interval echo "[Success]" } @@ -94,7 +95,7 @@ check_interval() check_event() { echo -n "Checking CSV output: event " - perf stat -x, -e cpu-clock true 2>&1 | commachecker --event + perf stat -x$csv_sep -e cpu-clock true 2>&1 | commachecker --event echo "[Success]" } @@ -106,7 +107,7 @@ check_per_core() echo "[Skip] paranoid and not root" return fi - perf stat -x, --per-core -a true 2>&1 | commachecker --per-core + perf stat -x$csv_sep --per-core -a true 2>&1 | commachecker --per-core echo "[Success]" } @@ -118,7 +119,7 @@ check_per_thread() echo "[Skip] paranoid and not root" return fi - perf stat -x, --per-thread -a true 2>&1 | commachecker --per-thread + perf stat -x$csv_sep --per-thread -a true 2>&1 | commachecker --per-thread echo "[Success]" } @@ -130,7 +131,7 @@ check_per_die() echo "[Skip] paranoid and not root" return fi - perf stat -x, --per-die -a true 2>&1 | commachecker --per-die + perf stat -x$csv_sep --per-die -a true 2>&1 | commachecker --per-die echo "[Success]" } @@ -142,7 +143,7 @@ check_per_node() echo "[Skip] paranoid and not root" return fi - perf stat -x, --per-node -a true 2>&1 | commachecker --per-node + perf stat -x$csv_sep --per-node -a true 2>&1 | commachecker --per-node echo "[Success]" } @@ -154,7 +155,7 @@ check_per_socket() echo "[Skip] paranoid and not root" return fi - perf stat -x, --per-socket -a true 2>&1 | commachecker --per-socket + perf stat -x$csv_sep --per-socket -a true 2>&1 | commachecker --per-socket echo "[Success]" } diff --git a/tools/perf/tests/shell/stat_all_metrics.sh b/tools/perf/tests/shell/stat_all_metrics.sh index 6e79349e42be..22e9cb294b40 100755 --- a/tools/perf/tests/shell/stat_all_metrics.sh +++ b/tools/perf/tests/shell/stat_all_metrics.sh @@ -11,7 +11,7 @@ for m in $(perf list --raw-dump metrics); do continue fi # Failed so try system wide. - result=$(perf stat -M "$m" -a true 2>&1) + result=$(perf stat -M "$m" -a sleep 0.01 2>&1) if [[ "$result" =~ "${m:0:50}" ]] then continue diff --git a/tools/perf/tests/shell/test_brstack.sh b/tools/perf/tests/shell/test_brstack.sh index 59195eb80052..1c49d8293003 100755 --- a/tools/perf/tests/shell/test_brstack.sh +++ b/tools/perf/tests/shell/test_brstack.sh @@ -30,14 +30,14 @@ test_user_branches() { # brstack_foo+0x14/brstack_bar+0x40/P/-/-/0/CALL set -x - grep -E -m1 "^brstack_bench\+[^ ]*/brstack_foo\+[^ ]*/IND_CALL$" $TMPDIR/perf.script - grep -E -m1 "^brstack_foo\+[^ ]*/brstack_bar\+[^ ]*/CALL$" $TMPDIR/perf.script - grep -E -m1 "^brstack_bench\+[^ ]*/brstack_foo\+[^ ]*/CALL$" $TMPDIR/perf.script - grep -E -m1 "^brstack_bench\+[^ ]*/brstack_bar\+[^ ]*/CALL$" $TMPDIR/perf.script - grep -E -m1 "^brstack_bar\+[^ ]*/brstack_foo\+[^ ]*/RET$" $TMPDIR/perf.script - grep -E -m1 "^brstack_foo\+[^ ]*/brstack_bench\+[^ ]*/RET$" $TMPDIR/perf.script - grep -E -m1 "^brstack_bench\+[^ ]*/brstack_bench\+[^ ]*/COND$" $TMPDIR/perf.script - grep -E -m1 "^brstack\+[^ ]*/brstack\+[^ ]*/UNCOND$" $TMPDIR/perf.script + grep -E -m1 "^brstack_bench\+[^ ]*/brstack_foo\+[^ ]*/IND_CALL/.*$" $TMPDIR/perf.script + grep -E -m1 "^brstack_foo\+[^ ]*/brstack_bar\+[^ ]*/CALL/.*$" $TMPDIR/perf.script + grep -E -m1 "^brstack_bench\+[^ ]*/brstack_foo\+[^ ]*/CALL/.*$" $TMPDIR/perf.script + grep -E -m1 "^brstack_bench\+[^ ]*/brstack_bar\+[^ ]*/CALL/.*$" $TMPDIR/perf.script + grep -E -m1 "^brstack_bar\+[^ ]*/brstack_foo\+[^ ]*/RET/.*$" $TMPDIR/perf.script + grep -E -m1 "^brstack_foo\+[^ ]*/brstack_bench\+[^ ]*/RET/.*$" $TMPDIR/perf.script + grep -E -m1 "^brstack_bench\+[^ ]*/brstack_bench\+[^ ]*/COND/.*$" $TMPDIR/perf.script + grep -E -m1 "^brstack\+[^ ]*/brstack\+[^ ]*/UNCOND/.*$" $TMPDIR/perf.script set +x # some branch types are still not being tested: @@ -57,7 +57,7 @@ test_filter() { # fail if we find any branch type that doesn't match any of the expected ones # also consider UNKNOWN branch types (-) - if grep -E -vm1 "^[^ ]*/($expect|-|( *))$" $TMPDIR/perf.script; then + if grep -E -vm1 "^[^ ]*/($expect|-|( *))/.*$" $TMPDIR/perf.script; then return 1 fi } diff --git a/tools/perf/tests/shell/test_intel_pt.sh b/tools/perf/tests/shell/test_intel_pt.sh index f5ed7b1af419..4ddb17cb83c5 100755 --- a/tools/perf/tests/shell/test_intel_pt.sh +++ b/tools/perf/tests/shell/test_intel_pt.sh @@ -620,6 +620,22 @@ test_event_trace() return 0 } +test_pipe() +{ + echo "--- Test with pipe mode ---" + # Check if it works with pipe + if ! perf_record_no_bpf -o- -e intel_pt//u uname | perf report -q -i- --itrace=i10000 ; then + echo "perf record + report failed with pipe mode" + return 1 + fi + if ! perf_record_no_bpf -o- -e intel_pt//u uname | perf inject -b > /dev/null ; then + echo "perf record + inject failed with pipe mode" + return 1 + fi + echo OK + return 0 +} + count_result() { if [ "$1" -eq 2 ] ; then @@ -647,6 +663,7 @@ test_virtual_lbr || ret=$? ; count_result $ret ; ret=0 test_power_event || ret=$? ; count_result $ret ; ret=0 test_no_tnt || ret=$? ; count_result $ret ; ret=0 test_event_trace || ret=$? ; count_result $ret ; ret=0 +test_pipe || ret=$? ; count_result $ret ; ret=0 cleanup diff --git a/tools/perf/tests/symbols.c b/tools/perf/tests/symbols.c new file mode 100644 index 000000000000..057b16df6416 --- /dev/null +++ b/tools/perf/tests/symbols.c @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: GPL-2.0 +#include <linux/compiler.h> +#include <linux/string.h> +#include <sys/mman.h> +#include <limits.h> +#include "debug.h" +#include "dso.h" +#include "machine.h" +#include "thread.h" +#include "symbol.h" +#include "map.h" +#include "util.h" +#include "tests.h" + +struct test_info { + struct machine *machine; + struct thread *thread; +}; + +static int init_test_info(struct test_info *ti) +{ + ti->machine = machine__new_host(); + if (!ti->machine) { + pr_debug("machine__new_host() failed!\n"); + return TEST_FAIL; + } + + /* Create a dummy thread */ + ti->thread = machine__findnew_thread(ti->machine, 100, 100); + if (!ti->thread) { + pr_debug("machine__findnew_thread() failed!\n"); + return TEST_FAIL; + } + + return TEST_OK; +} + +static void exit_test_info(struct test_info *ti) +{ + thread__put(ti->thread); + machine__delete(ti->machine); +} + +static void get_test_dso_filename(char *filename, size_t max_sz) +{ + if (dso_to_test) + strlcpy(filename, dso_to_test, max_sz); + else + perf_exe(filename, max_sz); +} + +static int create_map(struct test_info *ti, char *filename, struct map **map_p) +{ + /* Create a dummy map at 0x100000 */ + *map_p = map__new(ti->machine, 0x100000, 0xffffffff, 0, NULL, + PROT_EXEC, 0, NULL, filename, ti->thread); + if (!*map_p) { + pr_debug("Failed to create map!"); + return TEST_FAIL; + } + + return TEST_OK; +} + +static int test_dso(struct dso *dso) +{ + struct symbol *last_sym = NULL; + struct rb_node *nd; + int ret = TEST_OK; + + /* dso__fprintf() prints all the symbols */ + if (verbose > 1) + dso__fprintf(dso, stderr); + + for (nd = rb_first_cached(&dso->symbols); nd; nd = rb_next(nd)) { + struct symbol *sym = rb_entry(nd, struct symbol, rb_node); + + if (sym->type != STT_FUNC && sym->type != STT_GNU_IFUNC) + continue; + + /* Check for overlapping function symbols */ + if (last_sym && sym->start < last_sym->end) { + pr_debug("Overlapping symbols:\n"); + symbol__fprintf(last_sym, stderr); + symbol__fprintf(sym, stderr); + ret = TEST_FAIL; + } + /* Check for zero-length function symbol */ + if (sym->start == sym->end) { + pr_debug("Zero-length symbol:\n"); + symbol__fprintf(sym, stderr); + ret = TEST_FAIL; + } + last_sym = sym; + } + + return ret; +} + +static int test_file(struct test_info *ti, char *filename) +{ + struct map *map = NULL; + int ret, nr; + + pr_debug("Testing %s\n", filename); + + ret = create_map(ti, filename, &map); + if (ret != TEST_OK) + return ret; + + nr = dso__load(map->dso, map); + if (nr < 0) { + pr_debug("dso__load() failed!\n"); + ret = TEST_FAIL; + goto out_put; + } + + if (nr == 0) { + pr_debug("DSO has no symbols!\n"); + ret = TEST_SKIP; + goto out_put; + } + + ret = test_dso(map->dso); +out_put: + map__put(map); + + return ret; +} + +static int test__symbols(struct test_suite *test __maybe_unused, int subtest __maybe_unused) +{ + char filename[PATH_MAX]; + struct test_info ti; + int ret; + + ret = init_test_info(&ti); + if (ret != TEST_OK) + return ret; + + get_test_dso_filename(filename, sizeof(filename)); + + ret = test_file(&ti, filename); + + exit_test_info(&ti); + + return ret; +} + +DEFINE_SUITE("Symbols", symbols); diff --git a/tools/perf/tests/tests.h b/tools/perf/tests/tests.h index fb4b5ad4dd0f..9a0f3904e53d 100644 --- a/tools/perf/tests/tests.h +++ b/tools/perf/tests/tests.h @@ -148,6 +148,7 @@ DECLARE_SUITE(perf_time_to_tsc); DECLARE_SUITE(dlfilter); DECLARE_SUITE(sigtrap); DECLARE_SUITE(event_groups); +DECLARE_SUITE(symbols); /* * PowerPC and S390 do not support creation of instruction breakpoints using the @@ -208,4 +209,6 @@ DECLARE_WORKLOAD(sqrtloop); DECLARE_WORKLOAD(brstack); DECLARE_WORKLOAD(datasym); +extern const char *dso_to_test; + #endif /* TESTS_H */ diff --git a/tools/perf/tests/workloads/thloop.c b/tools/perf/tests/workloads/thloop.c index 29193b75717e..af05269c2eb8 100644 --- a/tools/perf/tests/workloads/thloop.c +++ b/tools/perf/tests/workloads/thloop.c @@ -20,7 +20,7 @@ static void sighandler(int sig __maybe_unused) noinline void test_loop(void) { while (!done) - count++; + __atomic_fetch_add(&count, 1, __ATOMIC_RELAXED); } static void *thfunc(void *arg) diff --git a/tools/perf/util/Build b/tools/perf/util/Build index 79b9498886a2..918b501f9bd8 100644 --- a/tools/perf/util/Build +++ b/tools/perf/util/Build @@ -154,7 +154,10 @@ perf-$(CONFIG_PERF_BPF_SKEL) += bpf_counter.o perf-$(CONFIG_PERF_BPF_SKEL) += bpf_counter_cgroup.o perf-$(CONFIG_PERF_BPF_SKEL) += bpf_ftrace.o perf-$(CONFIG_PERF_BPF_SKEL) += bpf_off_cpu.o -perf-$(CONFIG_PERF_BPF_SKEL) += bpf_lock_contention.o + +ifeq ($(CONFIG_LIBTRACEEVENT),y) + perf-$(CONFIG_PERF_BPF_SKEL) += bpf_lock_contention.o +endif ifeq ($(CONFIG_LIBTRACEEVENT),y) perf-$(CONFIG_PERF_BPF_SKEL) += bpf_kwork.o diff --git a/tools/perf/util/arm-spe-decoder/arm-spe-decoder.c b/tools/perf/util/arm-spe-decoder/arm-spe-decoder.c index 091987dd3966..40dcedfd75cd 100644 --- a/tools/perf/util/arm-spe-decoder/arm-spe-decoder.c +++ b/tools/perf/util/arm-spe-decoder/arm-spe-decoder.c @@ -68,7 +68,11 @@ static u64 arm_spe_calc_ip(int index, u64 payload) /* Clean highest byte */ payload = SPE_ADDR_PKT_ADDR_GET_BYTES_0_6(payload); } else { - pr_err("unsupported address packet index: 0x%x\n", index); + static u32 seen_idx = 0; + if (!(seen_idx & BIT(index))) { + seen_idx |= BIT(index); + pr_warning("ignoring unsupported address packet index: 0x%x\n", index); + } } return payload; diff --git a/tools/perf/util/arm-spe-decoder/arm-spe-pkt-decoder.c b/tools/perf/util/arm-spe-decoder/arm-spe-pkt-decoder.c index 2f311189c6e8..fed4741f372e 100644 --- a/tools/perf/util/arm-spe-decoder/arm-spe-pkt-decoder.c +++ b/tools/perf/util/arm-spe-decoder/arm-spe-pkt-decoder.c @@ -422,16 +422,18 @@ static int arm_spe_pkt_desc_addr(const struct arm_spe_pkt *packet, int ch, pat; u64 payload = packet->payload; int err = 0; + static const char *idx_name[] = {"PC", "TGT", "VA", "PA", "PBT"}; switch (idx) { case SPE_ADDR_PKT_HDR_INDEX_INS: case SPE_ADDR_PKT_HDR_INDEX_BRANCH: + case SPE_ADDR_PKT_HDR_INDEX_PREV_BRANCH: ns = !!SPE_ADDR_PKT_GET_NS(payload); el = SPE_ADDR_PKT_GET_EL(payload); payload = SPE_ADDR_PKT_ADDR_GET_BYTES_0_6(payload); arm_spe_pkt_out_string(&err, &buf, &buf_len, "%s 0x%llx el%d ns=%d", - (idx == 1) ? "TGT" : "PC", payload, el, ns); + idx_name[idx], payload, el, ns); break; case SPE_ADDR_PKT_HDR_INDEX_DATA_VIRT: arm_spe_pkt_out_string(&err, &buf, &buf_len, diff --git a/tools/perf/util/arm-spe-decoder/arm-spe-pkt-decoder.h b/tools/perf/util/arm-spe-decoder/arm-spe-pkt-decoder.h index 9b970e7bf1e2..f75ed3a8a050 100644 --- a/tools/perf/util/arm-spe-decoder/arm-spe-pkt-decoder.h +++ b/tools/perf/util/arm-spe-decoder/arm-spe-pkt-decoder.h @@ -65,6 +65,7 @@ struct arm_spe_pkt { #define SPE_ADDR_PKT_HDR_INDEX_BRANCH 0x1 #define SPE_ADDR_PKT_HDR_INDEX_DATA_VIRT 0x2 #define SPE_ADDR_PKT_HDR_INDEX_DATA_PHYS 0x3 +#define SPE_ADDR_PKT_HDR_INDEX_PREV_BRANCH 0x4 /* Address packet payload */ #define SPE_ADDR_PKT_ADDR_BYTE7_SHIFT 56 diff --git a/tools/perf/util/auxtrace.c b/tools/perf/util/auxtrace.c index c2e323cd7d49..498ff7f24463 100644 --- a/tools/perf/util/auxtrace.c +++ b/tools/perf/util/auxtrace.c @@ -1133,6 +1133,9 @@ int auxtrace_queue_data(struct perf_session *session, bool samples, bool events) if (auxtrace__dont_decode(session)) return 0; + if (perf_data__is_pipe(session->data)) + return 0; + if (!session->auxtrace || !session->auxtrace->queue_data) return -EINVAL; @@ -1391,6 +1394,7 @@ void itrace_synth_opts__set_default(struct itrace_synth_opts *synth_opts, synth_opts->calls = true; } else { synth_opts->instructions = true; + synth_opts->cycles = true; synth_opts->period_type = PERF_ITRACE_DEFAULT_PERIOD_TYPE; synth_opts->period = PERF_ITRACE_DEFAULT_PERIOD; } @@ -1479,7 +1483,11 @@ int itrace_do_parse_synth_opts(struct itrace_synth_opts *synth_opts, for (p = str; *p;) { switch (*p++) { case 'i': - synth_opts->instructions = true; + case 'y': + if (p[-1] == 'y') + synth_opts->cycles = true; + else + synth_opts->instructions = true; while (*p == ' ' || *p == ',') p += 1; if (isdigit(*p)) { @@ -1638,7 +1646,7 @@ int itrace_do_parse_synth_opts(struct itrace_synth_opts *synth_opts, } } out: - if (synth_opts->instructions) { + if (synth_opts->instructions || synth_opts->cycles) { if (!period_type_set) synth_opts->period_type = PERF_ITRACE_DEFAULT_PERIOD_TYPE; diff --git a/tools/perf/util/auxtrace.h b/tools/perf/util/auxtrace.h index 2cf63d377831..29eb82dff574 100644 --- a/tools/perf/util/auxtrace.h +++ b/tools/perf/util/auxtrace.h @@ -71,6 +71,9 @@ enum itrace_period_type { * @inject: indicates the event (not just the sample) must be fully synthesized * because 'perf inject' will write it out * @instructions: whether to synthesize 'instructions' events + * @cycles: whether to synthesize 'cycles' events + * (not fully accurate, since CYC packets are only emitted + * together with other events, such as branches) * @branches: whether to synthesize 'branches' events * (branch misses only for Arm SPE) * @transactions: whether to synthesize events for transactions @@ -119,6 +122,7 @@ struct itrace_synth_opts { bool default_no_sample; bool inject; bool instructions; + bool cycles; bool branches; bool transactions; bool ptwrites; @@ -643,6 +647,7 @@ bool auxtrace__evsel_is_auxtrace(struct perf_session *session, #define ITRACE_HELP \ " i[period]: synthesize instructions events\n" \ +" y[period]: synthesize cycles events (same period as i)\n" \ " b: synthesize branches events (branch misses for Arm SPE)\n" \ " c: synthesize branches events (calls only)\n" \ " r: synthesize branches events (returns only)\n" \ @@ -674,7 +679,7 @@ bool auxtrace__evsel_is_auxtrace(struct perf_session *session, " A: approximate IPC\n" \ " Z: prefer to ignore timestamps (so-called \"timeless\" decoding)\n" \ " PERIOD[ns|us|ms|i|t]: specify period to sample stream\n" \ -" concatenate multiple options. Default is ibxwpe or cewp\n" +" concatenate multiple options. Default is iybxwpe or cewp\n" static inline void itrace_synth_opts__set_time_range(struct itrace_synth_opts *opts, diff --git a/tools/perf/util/bpf_lock_contention.c b/tools/perf/util/bpf_lock_contention.c index 0236334fd69b..fadcacb9d501 100644 --- a/tools/perf/util/bpf_lock_contention.c +++ b/tools/perf/util/bpf_lock_contention.c @@ -34,13 +34,15 @@ int lock_contention_prepare(struct lock_contention *con) bpf_map__set_max_entries(skel->maps.lock_stat, con->map_nr_entries); bpf_map__set_max_entries(skel->maps.tstamp, con->map_nr_entries); - if (con->aggr_mode == LOCK_AGGR_TASK) { + if (con->aggr_mode == LOCK_AGGR_TASK) bpf_map__set_max_entries(skel->maps.task_data, con->map_nr_entries); - bpf_map__set_max_entries(skel->maps.stacks, 1); - } else { + else bpf_map__set_max_entries(skel->maps.task_data, 1); + + if (con->save_callstack) bpf_map__set_max_entries(skel->maps.stacks, con->map_nr_entries); - } + else + bpf_map__set_max_entries(skel->maps.stacks, 1); if (target__has_cpu(target)) ncpus = perf_cpu_map__nr(evlist->core.user_requested_cpus); @@ -146,6 +148,8 @@ int lock_contention_prepare(struct lock_contention *con) /* these don't work well if in the rodata section */ skel->bss->stack_skip = con->stack_skip; skel->bss->aggr_mode = con->aggr_mode; + skel->bss->needs_callstack = con->save_callstack; + skel->bss->lock_owner = con->owner; lock_contention_bpf__attach(skel); return 0; @@ -163,9 +167,70 @@ int lock_contention_stop(void) return 0; } +static const char *lock_contention_get_name(struct lock_contention *con, + struct contention_key *key, + u64 *stack_trace) +{ + int idx = 0; + u64 addr; + const char *name = ""; + static char name_buf[KSYM_NAME_LEN]; + struct symbol *sym; + struct map *kmap; + struct machine *machine = con->machine; + + if (con->aggr_mode == LOCK_AGGR_TASK) { + struct contention_task_data task; + int pid = key->pid; + int task_fd = bpf_map__fd(skel->maps.task_data); + + /* do not update idle comm which contains CPU number */ + if (pid) { + struct thread *t = __machine__findnew_thread(machine, /*pid=*/-1, pid); + + if (t == NULL) + return name; + if (!bpf_map_lookup_elem(task_fd, &pid, &task) && + thread__set_comm(t, task.comm, /*timestamp=*/0)) + name = task.comm; + } + return name; + } + + if (con->aggr_mode == LOCK_AGGR_ADDR) { + sym = machine__find_kernel_symbol(machine, key->lock_addr, &kmap); + if (sym) + name = sym->name; + return name; + } + + /* LOCK_AGGR_CALLER: skip lock internal functions */ + while (machine__is_lock_function(machine, stack_trace[idx]) && + idx < con->max_stack - 1) + idx++; + + addr = stack_trace[idx]; + sym = machine__find_kernel_symbol(machine, addr, &kmap); + + if (sym) { + unsigned long offset; + + offset = kmap->map_ip(kmap, addr) - sym->start; + + if (offset == 0) + return sym->name; + + snprintf(name_buf, sizeof(name_buf), "%s+%#lx", sym->name, offset); + } else { + snprintf(name_buf, sizeof(name_buf), "%#lx", (unsigned long)addr); + } + + return name_buf; +} + int lock_contention_read(struct lock_contention *con) { - int fd, stack, task_fd, err = 0; + int fd, stack, err = 0; struct contention_key *prev_key, key; struct contention_data data = {}; struct lock_stat *st = NULL; @@ -175,7 +240,6 @@ int lock_contention_read(struct lock_contention *con) fd = bpf_map__fd(skel->maps.lock_stat); stack = bpf_map__fd(skel->maps.stacks); - task_fd = bpf_map__fd(skel->maps.task_data); con->lost = skel->bss->lost; @@ -195,98 +259,75 @@ int lock_contention_read(struct lock_contention *con) prev_key = NULL; while (!bpf_map_get_next_key(fd, prev_key, &key)) { - struct map *kmap; - struct symbol *sym; - int idx = 0; - s32 stack_id; + s64 ls_key; + const char *name; /* to handle errors in the loop body */ err = -1; bpf_map_lookup_elem(fd, &key, &data); - st = zalloc(sizeof(*st)); - if (st == NULL) - break; - - st->nr_contended = data.count; - st->wait_time_total = data.total_time; - st->wait_time_max = data.max_time; - st->wait_time_min = data.min_time; - - if (data.count) - st->avg_wait_time = data.total_time / data.count; - - st->flags = data.flags; - st->addr = key.aggr_key; + if (con->save_callstack) { + bpf_map_lookup_elem(stack, &key.stack_id, stack_trace); - if (con->aggr_mode == LOCK_AGGR_TASK) { - struct contention_task_data task; - struct thread *t; - int pid = key.aggr_key; + if (!match_callstack_filter(machine, stack_trace)) + goto next; + } - /* do not update idle comm which contains CPU number */ - if (st->addr) { - bpf_map_lookup_elem(task_fd, &pid, &task); - t = __machine__findnew_thread(machine, /*pid=*/-1, pid); - thread__set_comm(t, task.comm, /*timestamp=*/0); - } + switch (con->aggr_mode) { + case LOCK_AGGR_CALLER: + ls_key = key.stack_id; + break; + case LOCK_AGGR_TASK: + ls_key = key.pid; + break; + case LOCK_AGGR_ADDR: + ls_key = key.lock_addr; + break; + default: goto next; } - if (con->aggr_mode == LOCK_AGGR_ADDR) { - sym = machine__find_kernel_symbol(machine, st->addr, &kmap); - if (sym) - st->name = strdup(sym->name); + st = lock_stat_find(ls_key); + if (st != NULL) { + st->wait_time_total += data.total_time; + if (st->wait_time_max < data.max_time) + st->wait_time_max = data.max_time; + if (st->wait_time_min > data.min_time) + st->wait_time_min = data.min_time; + + st->nr_contended += data.count; + if (st->nr_contended) + st->avg_wait_time = st->wait_time_total / st->nr_contended; goto next; } - stack_id = key.aggr_key; - bpf_map_lookup_elem(stack, &stack_id, stack_trace); - - /* skip lock internal functions */ - while (machine__is_lock_function(machine, stack_trace[idx]) && - idx < con->max_stack - 1) - idx++; - - st->addr = stack_trace[idx]; - sym = machine__find_kernel_symbol(machine, st->addr, &kmap); - - if (sym) { - unsigned long offset; - int ret = 0; - - offset = kmap->map_ip(kmap, st->addr) - sym->start; + name = lock_contention_get_name(con, &key, stack_trace); + st = lock_stat_findnew(ls_key, name, data.flags); + if (st == NULL) + break; - if (offset) - ret = asprintf(&st->name, "%s+%#lx", sym->name, offset); - else - st->name = strdup(sym->name); + st->nr_contended = data.count; + st->wait_time_total = data.total_time; + st->wait_time_max = data.max_time; + st->wait_time_min = data.min_time; - if (ret < 0 || st->name == NULL) - break; - } else if (asprintf(&st->name, "%#lx", (unsigned long)st->addr) < 0) { - break; - } + if (data.count) + st->avg_wait_time = data.total_time / data.count; - if (verbose > 0) { + if (con->save_callstack) { st->callstack = memdup(stack_trace, stack_size); if (st->callstack == NULL) break; } + next: - hlist_add_head(&st->hash_entry, con->result); prev_key = &key; - /* we're fine now, reset the values */ - st = NULL; + /* we're fine now, reset the error */ err = 0; } free(stack_trace); - if (st) { - free(st->name); - free(st); - } return err; } diff --git a/tools/perf/util/bpf_skel/lock_contention.bpf.c b/tools/perf/util/bpf_skel/lock_contention.bpf.c index ad0ca5d50557..e6007eaeda1a 100644 --- a/tools/perf/util/bpf_skel/lock_contention.bpf.c +++ b/tools/perf/util/bpf_skel/lock_contention.bpf.c @@ -10,6 +10,14 @@ /* default buffer size */ #define MAX_ENTRIES 10240 +/* lock contention flags from include/trace/events/lock.h */ +#define LCB_F_SPIN (1U << 0) +#define LCB_F_READ (1U << 1) +#define LCB_F_WRITE (1U << 2) +#define LCB_F_RT (1U << 3) +#define LCB_F_PERCPU (1U << 4) +#define LCB_F_MUTEX (1U << 5) + struct tstamp_data { __u64 timestamp; __u64 lock; @@ -76,13 +84,23 @@ struct { __uint(max_entries, 1); } addr_filter SEC(".maps"); +struct rw_semaphore___old { + struct task_struct *owner; +} __attribute__((preserve_access_index)); + +struct rw_semaphore___new { + atomic_long_t owner; +} __attribute__((preserve_access_index)); + /* control flags */ int enabled; int has_cpu; int has_task; int has_type; int has_addr; +int needs_callstack; int stack_skip; +int lock_owner; /* determine the key of lock stat */ int aggr_mode; @@ -131,17 +149,59 @@ static inline int can_record(u64 *ctx) return 1; } -static inline void update_task_data(__u32 pid) +static inline int update_task_data(struct task_struct *task) { struct contention_task_data *p; + int pid, err; + + err = bpf_core_read(&pid, sizeof(pid), &task->pid); + if (err) + return -1; p = bpf_map_lookup_elem(&task_data, &pid); if (p == NULL) { - struct contention_task_data data; + struct contention_task_data data = {}; - bpf_get_current_comm(data.comm, sizeof(data.comm)); + BPF_CORE_READ_STR_INTO(&data.comm, task, comm); bpf_map_update_elem(&task_data, &pid, &data, BPF_NOEXIST); } + + return 0; +} + +#ifndef __has_builtin +# define __has_builtin(x) 0 +#endif + +static inline struct task_struct *get_lock_owner(__u64 lock, __u32 flags) +{ + struct task_struct *task; + __u64 owner = 0; + + if (flags & LCB_F_MUTEX) { + struct mutex *mutex = (void *)lock; + owner = BPF_CORE_READ(mutex, owner.counter); + } else if (flags == LCB_F_READ || flags == LCB_F_WRITE) { +#if __has_builtin(bpf_core_type_matches) + if (bpf_core_type_matches(struct rw_semaphore___old)) { + struct rw_semaphore___old *rwsem = (void *)lock; + owner = (unsigned long)BPF_CORE_READ(rwsem, owner); + } else if (bpf_core_type_matches(struct rw_semaphore___new)) { + struct rw_semaphore___new *rwsem = (void *)lock; + owner = BPF_CORE_READ(rwsem, owner.counter); + } +#else + /* assume new struct */ + struct rw_semaphore *rwsem = (void *)lock; + owner = BPF_CORE_READ(rwsem, owner.counter); +#endif + } + + if (!owner) + return NULL; + + task = (void *)(owner & ~7UL); + return task; } SEC("tp_btf/contention_begin") @@ -173,11 +233,31 @@ int contention_begin(u64 *ctx) pelem->lock = (__u64)ctx[0]; pelem->flags = (__u32)ctx[1]; - if (aggr_mode == LOCK_AGGR_CALLER) { + if (needs_callstack) { pelem->stack_id = bpf_get_stackid(ctx, &stacks, BPF_F_FAST_STACK_CMP | stack_skip); if (pelem->stack_id < 0) lost++; + } else if (aggr_mode == LOCK_AGGR_TASK) { + struct task_struct *task; + + if (lock_owner) { + task = get_lock_owner(pelem->lock, pelem->flags); + + /* The flags is not used anymore. Pass the owner pid. */ + if (task) + pelem->flags = BPF_CORE_READ(task, pid); + else + pelem->flags = -1U; + + } else { + task = bpf_get_current_task_btf(); + } + + if (task) { + if (update_task_data(task) < 0 && lock_owner) + pelem->flags = -1U; + } } return 0; @@ -188,7 +268,7 @@ int contention_end(u64 *ctx) { __u32 pid; struct tstamp_data *pelem; - struct contention_key key; + struct contention_key key = {}; struct contention_data *data; __u64 duration; @@ -204,14 +284,20 @@ int contention_end(u64 *ctx) switch (aggr_mode) { case LOCK_AGGR_CALLER: - key.aggr_key = pelem->stack_id; + key.stack_id = pelem->stack_id; break; case LOCK_AGGR_TASK: - key.aggr_key = pid; - update_task_data(pid); + if (lock_owner) + key.pid = pelem->flags; + else + key.pid = pid; + if (needs_callstack) + key.stack_id = pelem->stack_id; break; case LOCK_AGGR_ADDR: - key.aggr_key = pelem->lock; + key.lock_addr = pelem->lock; + if (needs_callstack) + key.stack_id = pelem->stack_id; break; default: /* should not happen */ diff --git a/tools/perf/util/bpf_skel/lock_data.h b/tools/perf/util/bpf_skel/lock_data.h index ce71cf1a7e1e..3d35fd4407ac 100644 --- a/tools/perf/util/bpf_skel/lock_data.h +++ b/tools/perf/util/bpf_skel/lock_data.h @@ -4,7 +4,9 @@ #define UTIL_BPF_SKEL_LOCK_DATA_H struct contention_key { - u64 aggr_key; /* can be stack_id, pid or lock addr */ + u32 stack_id; + u32 pid; + u64 lock_addr; }; #define TASK_COMM_LEN 16 diff --git a/tools/perf/util/bpf_skel/off_cpu.bpf.c b/tools/perf/util/bpf_skel/off_cpu.bpf.c index 38e3b287dbb2..d877a0a9731f 100644 --- a/tools/perf/util/bpf_skel/off_cpu.bpf.c +++ b/tools/perf/util/bpf_skel/off_cpu.bpf.c @@ -277,7 +277,7 @@ int on_switch(u64 *ctx) else prev_state = get_task_state(prev); - return off_cpu_stat(ctx, prev, next, prev_state); + return off_cpu_stat(ctx, prev, next, prev_state & 0xff); } char LICENSE[] SEC("license") = "Dual BSD/GPL"; diff --git a/tools/perf/util/branch.c b/tools/perf/util/branch.c index 6d38238481d3..378f16a24751 100644 --- a/tools/perf/util/branch.c +++ b/tools/perf/util/branch.c @@ -212,3 +212,18 @@ int branch_type_str(struct branch_type_stat *st, char *bf, int size) return printed; } + +const char *branch_spec_desc(int spec) +{ + const char *branch_spec_outcomes[PERF_BR_SPEC_MAX] = { + "N/A", + "SPEC_WRONG_PATH", + "NON_SPEC_CORRECT_PATH", + "SPEC_CORRECT_PATH", + }; + + if (spec >= 0 && spec < PERF_BR_SPEC_MAX) + return branch_spec_outcomes[spec]; + + return NULL; +} diff --git a/tools/perf/util/branch.h b/tools/perf/util/branch.h index 3ed792db1125..e41bfffe2217 100644 --- a/tools/perf/util/branch.h +++ b/tools/perf/util/branch.h @@ -89,4 +89,6 @@ const char *get_branch_type(struct branch_entry *e); void branch_type_stat_display(FILE *fp, struct branch_type_stat *st); int branch_type_str(struct branch_type_stat *st, char *bf, int bfsize); +const char *branch_spec_desc(int spec); + #endif /* _PERF_BRANCH_H */ diff --git a/tools/perf/util/cacheline.h b/tools/perf/util/cacheline.h index dec8c0fb1f4a..fe6d5b60a031 100644 --- a/tools/perf/util/cacheline.h +++ b/tools/perf/util/cacheline.h @@ -6,16 +6,31 @@ int __pure cacheline_size(void); -static inline u64 cl_address(u64 address) + +/* + * Some architectures have 'Adjacent Cacheline Prefetch' feature, + * which performs like the cacheline size being doubled. + */ +static inline u64 cl_address(u64 address, bool double_cl) { + u64 size = cacheline_size(); + + if (double_cl) + size *= 2; + /* return the cacheline of the address */ - return (address & ~(cacheline_size() - 1)); + return (address & ~(size - 1)); } -static inline u64 cl_offset(u64 address) +static inline u64 cl_offset(u64 address, bool double_cl) { - /* return the cacheline of the address */ - return (address & (cacheline_size() - 1)); + u64 size = cacheline_size(); + + if (double_cl) + size *= 2; + + /* return the offset inside cacheline */ + return (address & (size - 1)); } #endif // PERF_CACHELINE_H diff --git a/tools/perf/util/cgroup.c b/tools/perf/util/cgroup.c index cd978c240e0d..bfb13306d82c 100644 --- a/tools/perf/util/cgroup.c +++ b/tools/perf/util/cgroup.c @@ -481,7 +481,6 @@ int evlist__expand_cgroup(struct evlist *evlist, const char *str, nr_cgroups++; if (metric_events) { - perf_stat__collect_metric_expr(tmp_list); if (metricgroup__copy_metric_events(tmp_list, cgrp, metric_events, &orig_metric_events) < 0) diff --git a/tools/perf/util/cputopo.c b/tools/perf/util/cputopo.c index 1a3ff6449158..e08797c3cdbc 100644 --- a/tools/perf/util/cputopo.c +++ b/tools/perf/util/cputopo.c @@ -422,8 +422,6 @@ void numa_topology__delete(struct numa_topology *tp) static int load_hybrid_node(struct hybrid_topology_node *node, struct perf_pmu *pmu) { - const char *sysfs; - char path[PATH_MAX]; char *buf = NULL, *p; FILE *fp; size_t len = 0; @@ -432,12 +430,7 @@ static int load_hybrid_node(struct hybrid_topology_node *node, if (!node->pmu_name) return -1; - sysfs = sysfs__mountpoint(); - if (!sysfs) - goto err; - - snprintf(path, PATH_MAX, CPUS_TEMPLATE_CPU, sysfs, pmu->name); - fp = fopen(path, "r"); + fp = perf_pmu__open_file(pmu, "cpus"); if (!fp) goto err; diff --git a/tools/perf/util/cs-etm-base.c b/tools/perf/util/cs-etm-base.c index 597542410854..5f48b756c4cf 100644 --- a/tools/perf/util/cs-etm-base.c +++ b/tools/perf/util/cs-etm-base.c @@ -36,7 +36,22 @@ static const char * const cs_etmv4_priv_fmts[] = { [CS_ETMV4_TRCIDR2] = " TRCIDR2 %llx\n", [CS_ETMV4_TRCIDR8] = " TRCIDR8 %llx\n", [CS_ETMV4_TRCAUTHSTATUS] = " TRCAUTHSTATUS %llx\n", - [CS_ETE_TRCDEVARCH] = " TRCDEVARCH %llx\n" + [CS_ETMV4_TS_SOURCE] = " TS_SOURCE %lld\n", +}; + +static const char * const cs_ete_priv_fmts[] = { + [CS_ETM_MAGIC] = " Magic number %llx\n", + [CS_ETM_CPU] = " CPU %lld\n", + [CS_ETM_NR_TRC_PARAMS] = " NR_TRC_PARAMS %llx\n", + [CS_ETE_TRCCONFIGR] = " TRCCONFIGR %llx\n", + [CS_ETE_TRCTRACEIDR] = " TRCTRACEIDR %llx\n", + [CS_ETE_TRCIDR0] = " TRCIDR0 %llx\n", + [CS_ETE_TRCIDR1] = " TRCIDR1 %llx\n", + [CS_ETE_TRCIDR2] = " TRCIDR2 %llx\n", + [CS_ETE_TRCIDR8] = " TRCIDR8 %llx\n", + [CS_ETE_TRCAUTHSTATUS] = " TRCAUTHSTATUS %llx\n", + [CS_ETE_TRCDEVARCH] = " TRCDEVARCH %llx\n", + [CS_ETE_TS_SOURCE] = " TS_SOURCE %lld\n", }; static const char * const param_unk_fmt = @@ -96,19 +111,22 @@ static int cs_etm__print_cpu_metadata_v1(u64 *val, int *offset) else fprintf(stdout, cs_etm_priv_fmts[j], val[i]); } - } else if (magic == __perf_cs_etmv4_magic || magic == __perf_cs_ete_magic) { - /* - * ETE and ETMv4 can be printed in the same block because the number of parameters - * is saved and they share the list of parameter names. ETE is also only supported - * in V1 files. - */ + } else if (magic == __perf_cs_etmv4_magic) { for (j = 0; j < total_params; j++, i++) { /* if newer record - could be excess params */ - if (j >= CS_ETE_PRIV_MAX) + if (j >= CS_ETMV4_PRIV_MAX) fprintf(stdout, param_unk_fmt, j, val[i]); else fprintf(stdout, cs_etmv4_priv_fmts[j], val[i]); } + } else if (magic == __perf_cs_ete_magic) { + for (j = 0; j < total_params; j++, i++) { + /* if newer record - could be excess params */ + if (j >= CS_ETE_PRIV_MAX) + fprintf(stdout, param_unk_fmt, j, val[i]); + else + fprintf(stdout, cs_ete_priv_fmts[j], val[i]); + } } else { /* failure - note bad magic value and error out */ fprintf(stdout, magic_unk_fmt, magic); diff --git a/tools/perf/util/cs-etm-decoder/cs-etm-decoder.c b/tools/perf/util/cs-etm-decoder/cs-etm-decoder.c index 31fa3b45134a..d0e521dfcf35 100644 --- a/tools/perf/util/cs-etm-decoder/cs-etm-decoder.c +++ b/tools/perf/util/cs-etm-decoder/cs-etm-decoder.c @@ -30,6 +30,15 @@ #endif #endif +/* + * Assume a maximum of 0.1ns elapsed per instruction. This would be the + * case with a theoretical 10GHz core executing 1 instruction per cycle. + * Used to estimate the sample time for synthesized instructions because + * Coresight only emits a timestamp for a range of instructions rather + * than per instruction. + */ +const u32 INSTR_PER_NS = 10; + struct cs_etm_decoder { void *data; void (*packet_printer)(const char *msg); @@ -112,6 +121,20 @@ int cs_etm_decoder__get_packet(struct cs_etm_packet_queue *packet_queue, return 1; } +/* + * Calculate the number of nanoseconds elapsed. + * + * instr_count is updated in place with the remainder of the instructions + * which didn't make up a whole nanosecond. + */ +static u32 cs_etm_decoder__dec_instr_count_to_ns(u32 *instr_count) +{ + const u32 instr_copy = *instr_count; + + *instr_count %= INSTR_PER_NS; + return instr_copy / INSTR_PER_NS; +} + static int cs_etm_decoder__gen_etmv3_config(struct cs_etm_trace_params *params, ocsd_etmv3_cfg *config) { @@ -260,15 +283,17 @@ cs_etm_decoder__do_soft_timestamp(struct cs_etm_queue *etmq, struct cs_etm_packet_queue *packet_queue, const uint8_t trace_chan_id) { + u64 estimated_ts; + /* No timestamp packet has been received, nothing to do */ - if (!packet_queue->cs_timestamp) + if (!packet_queue->next_cs_timestamp) return OCSD_RESP_CONT; - packet_queue->cs_timestamp = packet_queue->next_cs_timestamp; + estimated_ts = packet_queue->cs_timestamp + + cs_etm_decoder__dec_instr_count_to_ns(&packet_queue->instr_count); - /* Estimate the timestamp for the next range packet */ - packet_queue->next_cs_timestamp += packet_queue->instr_count; - packet_queue->instr_count = 0; + /* Estimated TS can never be higher than the next real one in the trace */ + packet_queue->cs_timestamp = min(packet_queue->next_cs_timestamp, estimated_ts); /* Tell the front end which traceid_queue needs attention */ cs_etm__etmq_set_traceid_queue_timestamp(etmq, trace_chan_id); @@ -283,6 +308,8 @@ cs_etm_decoder__do_hard_timestamp(struct cs_etm_queue *etmq, const ocsd_trc_index_t indx) { struct cs_etm_packet_queue *packet_queue; + u64 converted_timestamp; + u64 estimated_first_ts; /* First get the packet queue for this traceID */ packet_queue = cs_etm__etmq_get_packet_queue(etmq, trace_chan_id); @@ -290,17 +317,28 @@ cs_etm_decoder__do_hard_timestamp(struct cs_etm_queue *etmq, return OCSD_RESP_FATAL_SYS_ERR; /* + * Coresight timestamps are raw timer values which need to be scaled to ns. Assume + * 0 is a bad value so don't try to convert it. + */ + converted_timestamp = elem->timestamp ? + cs_etm__convert_sample_time(etmq, elem->timestamp) : 0; + + /* * We've seen a timestamp packet before - simply record the new value. * Function do_soft_timestamp() will report the value to the front end, * hence asking the decoder to keep decoding rather than stopping. */ - if (packet_queue->cs_timestamp) { - packet_queue->next_cs_timestamp = elem->timestamp; + if (packet_queue->next_cs_timestamp) { + /* + * What was next is now where new ranges start from, overwriting + * any previous estimate in cs_timestamp + */ + packet_queue->cs_timestamp = packet_queue->next_cs_timestamp; + packet_queue->next_cs_timestamp = converted_timestamp; return OCSD_RESP_CONT; } - - if (!elem->timestamp) { + if (!converted_timestamp) { /* * Zero timestamps can be seen due to misconfiguration or hardware bugs. * Warn once, and don't try to subtract instr_count as it would result in an @@ -312,7 +350,7 @@ cs_etm_decoder__do_hard_timestamp(struct cs_etm_queue *etmq, ". Decoding may be improved by prepending 'Z' to your current --itrace arguments.\n", indx); - } else if (packet_queue->instr_count > elem->timestamp) { + } else if (packet_queue->instr_count / INSTR_PER_NS > converted_timestamp) { /* * Sanity check that the elem->timestamp - packet_queue->instr_count would not * result in an underflow. Warn and clamp at 0 if it would. @@ -325,11 +363,14 @@ cs_etm_decoder__do_hard_timestamp(struct cs_etm_queue *etmq, * or a discontinuity. Since timestamps packets are generated *after* * range packets have been generated, we need to estimate the time at * which instructions started by subtracting the number of instructions - * executed to the timestamp. + * executed to the timestamp. Don't estimate earlier than the last used + * timestamp though. */ - packet_queue->cs_timestamp = elem->timestamp - packet_queue->instr_count; + estimated_first_ts = converted_timestamp - + (packet_queue->instr_count / INSTR_PER_NS); + packet_queue->cs_timestamp = max(packet_queue->cs_timestamp, estimated_first_ts); } - packet_queue->next_cs_timestamp = elem->timestamp; + packet_queue->next_cs_timestamp = converted_timestamp; packet_queue->instr_count = 0; /* Tell the front end which traceid_queue needs attention */ @@ -342,7 +383,6 @@ cs_etm_decoder__do_hard_timestamp(struct cs_etm_queue *etmq, static void cs_etm_decoder__reset_timestamp(struct cs_etm_packet_queue *packet_queue) { - packet_queue->cs_timestamp = 0; packet_queue->next_cs_timestamp = 0; packet_queue->instr_count = 0; } @@ -604,6 +644,9 @@ static ocsd_datapath_resp_t cs_etm_decoder__gen_trace_elem_printer( case OCSD_GEN_TRC_ELEM_CUSTOM: case OCSD_GEN_TRC_ELEM_SYNC_MARKER: case OCSD_GEN_TRC_ELEM_MEMTRANS: +#if (OCSD_VER_NUM >= 0x010400) + case OCSD_GEN_TRC_ELEM_INSTRUMENTATION: +#endif default: break; } diff --git a/tools/perf/util/cs-etm.c b/tools/perf/util/cs-etm.c index 33303d03c2fa..f65bac5ddbdb 100644 --- a/tools/perf/util/cs-etm.c +++ b/tools/perf/util/cs-etm.c @@ -35,6 +35,7 @@ #include "tool.h" #include "thread.h" #include "thread-stack.h" +#include "tsc.h" #include <tools/libc_compat.h> #include "util/synthetic-events.h" @@ -46,10 +47,12 @@ struct cs_etm_auxtrace { struct perf_session *session; struct machine *machine; struct thread *unknown_thread; + struct perf_tsc_conversion tc; u8 timeless_decoding; u8 snapshot_mode; u8 data_queued; + u8 has_virtual_ts; /* Virtual/Kernel timestamps in the trace. */ int num_cpu; u64 latest_kernel_timestamp; @@ -464,12 +467,12 @@ static void cs_etm__set_trace_param_ete(struct cs_etm_trace_params *t_params, u64 **metadata = etm->metadata; t_params[idx].protocol = CS_ETM_PROTO_ETE; - t_params[idx].ete.reg_idr0 = metadata[idx][CS_ETMV4_TRCIDR0]; - t_params[idx].ete.reg_idr1 = metadata[idx][CS_ETMV4_TRCIDR1]; - t_params[idx].ete.reg_idr2 = metadata[idx][CS_ETMV4_TRCIDR2]; - t_params[idx].ete.reg_idr8 = metadata[idx][CS_ETMV4_TRCIDR8]; - t_params[idx].ete.reg_configr = metadata[idx][CS_ETMV4_TRCCONFIGR]; - t_params[idx].ete.reg_traceidr = metadata[idx][CS_ETMV4_TRCTRACEIDR]; + t_params[idx].ete.reg_idr0 = metadata[idx][CS_ETE_TRCIDR0]; + t_params[idx].ete.reg_idr1 = metadata[idx][CS_ETE_TRCIDR1]; + t_params[idx].ete.reg_idr2 = metadata[idx][CS_ETE_TRCIDR2]; + t_params[idx].ete.reg_idr8 = metadata[idx][CS_ETE_TRCIDR8]; + t_params[idx].ete.reg_configr = metadata[idx][CS_ETE_TRCCONFIGR]; + t_params[idx].ete.reg_traceidr = metadata[idx][CS_ETE_TRCTRACEIDR]; t_params[idx].ete.reg_devarch = metadata[idx][CS_ETE_TRCDEVARCH]; } @@ -1161,6 +1164,30 @@ static void cs_etm__copy_insn(struct cs_etm_queue *etmq, sample->insn_len, (void *)sample->insn); } +u64 cs_etm__convert_sample_time(struct cs_etm_queue *etmq, u64 cs_timestamp) +{ + struct cs_etm_auxtrace *etm = etmq->etm; + + if (etm->has_virtual_ts) + return tsc_to_perf_time(cs_timestamp, &etm->tc); + else + return cs_timestamp; +} + +static inline u64 cs_etm__resolve_sample_time(struct cs_etm_queue *etmq, + struct cs_etm_traceid_queue *tidq) +{ + struct cs_etm_auxtrace *etm = etmq->etm; + struct cs_etm_packet_queue *packet_queue = &tidq->packet_queue; + + if (etm->timeless_decoding) + return 0; + else if (etm->has_virtual_ts) + return packet_queue->cs_timestamp; + else + return etm->latest_kernel_timestamp; +} + static int cs_etm__synth_instruction_sample(struct cs_etm_queue *etmq, struct cs_etm_traceid_queue *tidq, u64 addr, u64 period) @@ -1174,8 +1201,9 @@ static int cs_etm__synth_instruction_sample(struct cs_etm_queue *etmq, event->sample.header.misc = cs_etm__cpu_mode(etmq, addr); event->sample.header.size = sizeof(struct perf_event_header); - if (!etm->timeless_decoding) - sample.time = etm->latest_kernel_timestamp; + /* Set time field based on etm auxtrace config. */ + sample.time = cs_etm__resolve_sample_time(etmq, tidq); + sample.ip = addr; sample.pid = tidq->pid; sample.tid = tidq->tid; @@ -1232,8 +1260,9 @@ static int cs_etm__synth_branch_sample(struct cs_etm_queue *etmq, event->sample.header.misc = cs_etm__cpu_mode(etmq, ip); event->sample.header.size = sizeof(struct perf_event_header); - if (!etm->timeless_decoding) - sample.time = etm->latest_kernel_timestamp; + /* Set time field based on etm auxtrace config. */ + sample.time = cs_etm__resolve_sample_time(etmq, tidq); + sample.ip = ip; sample.pid = tidq->pid; sample.tid = tidq->tid; @@ -2746,12 +2775,42 @@ static int cs_etm__queue_aux_records(struct perf_session *session) return 0; } +#define HAS_PARAM(j, type, param) (metadata[(j)][CS_ETM_NR_TRC_PARAMS] <= \ + (CS_##type##_##param - CS_ETM_COMMON_BLK_MAX_V1)) + +/* + * Loop through the ETMs and complain if we find at least one where ts_source != 1 (virtual + * timestamps). + */ +static bool cs_etm__has_virtual_ts(u64 **metadata, int num_cpu) +{ + int j; + + for (j = 0; j < num_cpu; j++) { + switch (metadata[j][CS_ETM_MAGIC]) { + case __perf_cs_etmv4_magic: + if (HAS_PARAM(j, ETMV4, TS_SOURCE) || metadata[j][CS_ETMV4_TS_SOURCE] != 1) + return false; + break; + case __perf_cs_ete_magic: + if (HAS_PARAM(j, ETE, TS_SOURCE) || metadata[j][CS_ETE_TS_SOURCE] != 1) + return false; + break; + default: + /* Unknown / unsupported magic number. */ + return false; + } + } + return true; +} + int cs_etm__process_auxtrace_info_full(union perf_event *event, struct perf_session *session) { struct perf_record_auxtrace_info *auxtrace_info = &event->auxtrace_info; struct cs_etm_auxtrace *etm = NULL; struct int_node *inode; + struct perf_record_time_conv *tc = &session->time_conv; int event_header_size = sizeof(struct perf_event_header); int total_size = auxtrace_info->header.size; int priv_size = 0; @@ -2886,6 +2945,13 @@ int cs_etm__process_auxtrace_info_full(union perf_event *event, etm->auxtrace_type = auxtrace_info->type; etm->timeless_decoding = cs_etm__is_timeless_decoding(etm); + /* Use virtual timestamps if all ETMs report ts_source = 1 */ + etm->has_virtual_ts = cs_etm__has_virtual_ts(metadata, num_cpu); + + if (!etm->has_virtual_ts) + ui__warning("Virtual timestamps are not enabled, or not supported by the traced system.\n" + "The time field of the samples will not be set accurately.\n\n"); + etm->auxtrace.process_event = cs_etm__process_event; etm->auxtrace.process_auxtrace_event = cs_etm__process_auxtrace_event; etm->auxtrace.flush_events = cs_etm__flush_events; @@ -2915,6 +2981,15 @@ int cs_etm__process_auxtrace_info_full(union perf_event *event, goto err_delete_thread; } + etm->tc.time_shift = tc->time_shift; + etm->tc.time_mult = tc->time_mult; + etm->tc.time_zero = tc->time_zero; + if (event_contains(*tc, time_cycles)) { + etm->tc.time_cycles = tc->time_cycles; + etm->tc.time_mask = tc->time_mask; + etm->tc.cap_user_time_zero = tc->cap_user_time_zero; + etm->tc.cap_user_time_short = tc->cap_user_time_short; + } err = cs_etm__synth_events(etm, session); if (err) goto err_delete_thread; diff --git a/tools/perf/util/cs-etm.h b/tools/perf/util/cs-etm.h index 5da50d5dae6b..98a4f7113d2f 100644 --- a/tools/perf/util/cs-etm.h +++ b/tools/perf/util/cs-etm.h @@ -71,6 +71,7 @@ enum { CS_ETMV4_TRCIDR2, CS_ETMV4_TRCIDR8, CS_ETMV4_TRCAUTHSTATUS, + CS_ETMV4_TS_SOURCE, CS_ETMV4_PRIV_MAX, }; @@ -82,7 +83,17 @@ enum { * added in header V1 */ enum { - CS_ETE_TRCDEVARCH = CS_ETMV4_PRIV_MAX, + /* Dynamic, configurable parameters */ + CS_ETE_TRCCONFIGR = CS_ETM_COMMON_BLK_MAX_V1, + CS_ETE_TRCTRACEIDR, + /* RO, taken from sysFS */ + CS_ETE_TRCIDR0, + CS_ETE_TRCIDR1, + CS_ETE_TRCIDR2, + CS_ETE_TRCIDR8, + CS_ETE_TRCAUTHSTATUS, + CS_ETE_TRCDEVARCH, + CS_ETE_TS_SOURCE, CS_ETE_PRIV_MAX }; @@ -181,7 +192,7 @@ struct cs_etm_packet_queue { u32 head; u32 tail; u32 instr_count; - u64 cs_timestamp; + u64 cs_timestamp; /* Timestamp from trace data, converted to ns if possible */ u64 next_cs_timestamp; struct cs_etm_packet packet_buffer[CS_ETM_PACKET_MAX_BUFFER]; }; @@ -220,6 +231,7 @@ struct cs_etm_packet_queue *cs_etm__etmq_get_packet_queue(struct cs_etm_queue *etmq, u8 trace_chan_id); int cs_etm__process_auxtrace_info_full(union perf_event *event __maybe_unused, struct perf_session *session __maybe_unused); +u64 cs_etm__convert_sample_time(struct cs_etm_queue *etmq, u64 cs_timestamp); #else static inline int cs_etm__process_auxtrace_info_full(union perf_event *event __maybe_unused, diff --git a/tools/perf/util/data-convert-bt.c b/tools/perf/util/data-convert-bt.c index b842273458b8..2b732bccabad 100644 --- a/tools/perf/util/data-convert-bt.c +++ b/tools/perf/util/data-convert-bt.c @@ -322,10 +322,8 @@ static int add_tracepoint_field_value(struct ctf_writer *cw, offset = tmp_val; len = offset >> 16; offset &= 0xffff; -#ifdef HAVE_LIBTRACEEVENT_TEP_FIELD_IS_RELATIVE - if (flags & TEP_FIELD_IS_RELATIVE) + if (tep_field_is_relative(flags)) offset += fmtf->offset + fmtf->size; -#endif } if (flags & TEP_FIELD_IS_ARRAY) { diff --git a/tools/perf/util/debug.c b/tools/perf/util/debug.c index 190e818a0717..88378c4c5dd9 100644 --- a/tools/perf/util/debug.c +++ b/tools/perf/util/debug.c @@ -19,12 +19,19 @@ #include "debug.h" #include "print_binary.h" #include "target.h" +#include "trace-event.h" #include "ui/helpline.h" #include "ui/ui.h" #include "util/parse-sublevel-options.h" #include <linux/ctype.h> +#ifdef HAVE_LIBTRACEEVENT +#include <traceevent/event-parse.h> +#else +#define LIBTRACEEVENT_VERSION 0 +#endif + int verbose; int debug_peo_args; bool dump_trace = false, quiet = false; @@ -228,6 +235,14 @@ int perf_debug_option(const char *str) /* Allow only verbose value in range (0, 10), otherwise set 0. */ verbose = (verbose < 0) || (verbose > 10) ? 0 : verbose; +#if LIBTRACEEVENT_VERSION >= MAKE_LIBTRACEEVENT_VERSION(1, 3, 0) + if (verbose == 1) + tep_set_loglevel(TEP_LOG_INFO); + else if (verbose == 2) + tep_set_loglevel(TEP_LOG_DEBUG); + else if (verbose >= 3) + tep_set_loglevel(TEP_LOG_ALL); +#endif return 0; } diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c index 999dd1700502..51e8ce6edddc 100644 --- a/tools/perf/util/evsel.c +++ b/tools/perf/util/evsel.c @@ -285,8 +285,6 @@ void evsel__init(struct evsel *evsel, evsel->sample_size = __evsel__sample_size(attr->sample_type); evsel__calc_id_pos(evsel); evsel->cmdline_group_boundary = false; - evsel->metric_expr = NULL; - evsel->metric_name = NULL; evsel->metric_events = NULL; evsel->per_pkg_mask = NULL; evsel->collect_stat = false; @@ -2319,7 +2317,10 @@ u64 evsel__bitfield_swap_branch_flags(u64 value) * abort:1 //transaction abort * cycles:16 //cycle count to last branch * type:4 //branch type - * reserved:40 + * spec:2 //branch speculation info + * new_type:4 //additional branch type + * priv:3 //privilege level + * reserved:31 * } * } * @@ -2335,7 +2336,10 @@ u64 evsel__bitfield_swap_branch_flags(u64 value) new_val |= bitfield_swap(value, 3, 1); new_val |= bitfield_swap(value, 4, 16); new_val |= bitfield_swap(value, 20, 4); - new_val |= bitfield_swap(value, 24, 40); + new_val |= bitfield_swap(value, 24, 2); + new_val |= bitfield_swap(value, 26, 4); + new_val |= bitfield_swap(value, 30, 3); + new_val |= bitfield_swap(value, 33, 31); } else { new_val = bitfield_swap(value, 63, 1); new_val |= bitfield_swap(value, 62, 1); @@ -2343,7 +2347,10 @@ u64 evsel__bitfield_swap_branch_flags(u64 value) new_val |= bitfield_swap(value, 60, 1); new_val |= bitfield_swap(value, 44, 16); new_val |= bitfield_swap(value, 40, 4); - new_val |= bitfield_swap(value, 0, 40); + new_val |= bitfield_swap(value, 38, 2); + new_val |= bitfield_swap(value, 34, 4); + new_val |= bitfield_swap(value, 31, 3); + new_val |= bitfield_swap(value, 0, 31); } return new_val; @@ -2784,10 +2791,8 @@ void *evsel__rawptr(struct evsel *evsel, struct perf_sample *sample, const char if (field->flags & TEP_FIELD_IS_DYNAMIC) { offset = *(int *)(sample->raw_data + field->offset); offset &= 0xffff; -#ifdef HAVE_LIBTRACEEVENT_TEP_FIELD_IS_RELATIVE - if (field->flags & TEP_FIELD_IS_RELATIVE) + if (tep_field_is_relative(field->flags)) offset += field->offset + field->size; -#endif } return sample->raw_data + offset; diff --git a/tools/perf/util/evsel.h b/tools/perf/util/evsel.h index d572be41b960..24cb807ef6ce 100644 --- a/tools/perf/util/evsel.h +++ b/tools/perf/util/evsel.h @@ -105,8 +105,6 @@ struct evsel { * metric fields are similar, but needs more care as they can have * references to other metric (evsel). */ - const char * metric_expr; - const char * metric_name; struct evsel **metric_events; struct evsel *metric_leader; diff --git a/tools/perf/util/evswitch.h b/tools/perf/util/evswitch.h index fd30460b6218..8ffdbe526d98 100644 --- a/tools/perf/util/evswitch.h +++ b/tools/perf/util/evswitch.h @@ -22,9 +22,9 @@ bool evswitch__discard(struct evswitch *evswitch, struct evsel *evsel); #define OPTS_EVSWITCH(evswitch) \ OPT_STRING(0, "switch-on", &(evswitch)->on_name, \ - "event", "Consider events after the ocurrence of this event"), \ + "event", "Consider events after the occurrence of this event"), \ OPT_STRING(0, "switch-off", &(evswitch)->off_name, \ - "event", "Stop considering events after the ocurrence of this event"), \ + "event", "Stop considering events after the occurrence of this event"), \ OPT_BOOLEAN(0, "show-on-off-events", &(evswitch)->show_on_off_events, \ "Show the on/off switch events, used with --switch-on and --switch-off") diff --git a/tools/perf/util/expr.c b/tools/perf/util/expr.c index 00dcde35e0d3..c1da20b868db 100644 --- a/tools/perf/util/expr.c +++ b/tools/perf/util/expr.c @@ -19,6 +19,7 @@ #include <linux/zalloc.h> #include <ctype.h> #include <math.h> +#include "pmu.h" #ifdef PARSER_DEBUG extern int expr_debug; @@ -448,6 +449,10 @@ double expr__get_literal(const char *literal, const struct expr_scanner_ctx *ctx result = topology->core_cpus_lists; goto out; } + if (!strcmp("#slots", literal)) { + result = perf_pmu__cpu_slots_per_cycle(); + goto out; + } pr_err("Unrecognized literal '%s'", literal); out: diff --git a/tools/perf/util/expr.h b/tools/perf/util/expr.h index 029271540fb0..eaa44b24c555 100644 --- a/tools/perf/util/expr.h +++ b/tools/perf/util/expr.h @@ -9,6 +9,7 @@ struct expr_scanner_ctx { char *user_requested_cpu_list; int runtime; bool system_wide; + bool is_test; }; struct expr_parse_ctx { diff --git a/tools/perf/util/expr.l b/tools/perf/util/expr.l index d47de5f270a8..4fbf353e78e7 100644 --- a/tools/perf/util/expr.l +++ b/tools/perf/util/expr.l @@ -87,9 +87,11 @@ static int literal(yyscan_t scanner, const struct expr_scanner_ctx *sctx) YYSTYPE *yylval = expr_get_lval(scanner); yylval->num = expr__get_literal(expr_get_text(scanner), sctx); - if (isnan(yylval->num)) - return EXPR_ERROR; - + if (isnan(yylval->num)) { + if (!sctx->is_test) + return EXPR_ERROR; + yylval->num = 1; + } return LITERAL; } %} diff --git a/tools/perf/util/intel-pt.c b/tools/perf/util/intel-pt.c index 6d3921627e33..955c1b9dc6a4 100644 --- a/tools/perf/util/intel-pt.c +++ b/tools/perf/util/intel-pt.c @@ -5,6 +5,7 @@ */ #include <inttypes.h> +#include <linux/perf_event.h> #include <stdio.h> #include <stdbool.h> #include <errno.h> @@ -98,6 +99,10 @@ struct intel_pt { u64 instructions_sample_type; u64 instructions_id; + bool sample_cycles; + u64 cycles_sample_type; + u64 cycles_id; + bool sample_branches; u32 branches_filter; u64 branches_sample_type; @@ -214,6 +219,8 @@ struct intel_pt_queue { u64 ipc_cyc_cnt; u64 last_in_insn_cnt; u64 last_in_cyc_cnt; + u64 last_cy_insn_cnt; + u64 last_cy_cyc_cnt; u64 last_br_insn_cnt; u64 last_br_cyc_cnt; unsigned int cbr_seen; @@ -1319,7 +1326,7 @@ static struct intel_pt_queue *intel_pt_alloc_queue(struct intel_pt *pt, if (pt->filts.cnt > 0) params.pgd_ip = intel_pt_pgd_ip; - if (pt->synth_opts.instructions) { + if (pt->synth_opts.instructions || pt->synth_opts.cycles) { if (pt->synth_opts.period) { switch (pt->synth_opts.period_type) { case PERF_ITRACE_PERIOD_INSTRUCTIONS: @@ -1830,6 +1837,33 @@ static int intel_pt_synth_instruction_sample(struct intel_pt_queue *ptq) pt->instructions_sample_type); } +static int intel_pt_synth_cycle_sample(struct intel_pt_queue *ptq) +{ + struct intel_pt *pt = ptq->pt; + union perf_event *event = ptq->event_buf; + struct perf_sample sample = { .ip = 0, }; + u64 period = 0; + + if (ptq->sample_ipc) + period = ptq->ipc_cyc_cnt - ptq->last_cy_cyc_cnt; + + if (!period || intel_pt_skip_event(pt)) + return 0; + + intel_pt_prep_sample(pt, ptq, event, &sample); + + sample.id = ptq->pt->cycles_id; + sample.stream_id = ptq->pt->cycles_id; + sample.period = period; + + sample.cyc_cnt = period; + sample.insn_cnt = ptq->ipc_insn_cnt - ptq->last_cy_insn_cnt; + ptq->last_cy_insn_cnt = ptq->ipc_insn_cnt; + ptq->last_cy_cyc_cnt = ptq->ipc_cyc_cnt; + + return intel_pt_deliver_synth_event(pt, event, &sample, pt->cycles_sample_type); +} + static int intel_pt_synth_transaction_sample(struct intel_pt_queue *ptq) { struct intel_pt *pt = ptq->pt; @@ -2598,10 +2632,17 @@ static int intel_pt_sample(struct intel_pt_queue *ptq) } } - if (pt->sample_instructions && (state->type & INTEL_PT_INSTRUCTION)) { - err = intel_pt_synth_instruction_sample(ptq); - if (err) - return err; + if (state->type & INTEL_PT_INSTRUCTION) { + if (pt->sample_instructions) { + err = intel_pt_synth_instruction_sample(ptq); + if (err) + return err; + } + if (pt->sample_cycles) { + err = intel_pt_synth_cycle_sample(ptq); + if (err) + return err; + } } if (pt->sample_transactions && (state->type & INTEL_PT_TRANSACTION)) { @@ -3731,6 +3772,22 @@ static int intel_pt_synth_events(struct intel_pt *pt, id += 1; } + if (pt->synth_opts.cycles) { + attr.config = PERF_COUNT_HW_CPU_CYCLES; + if (pt->synth_opts.period_type == PERF_ITRACE_PERIOD_NANOSECS) + attr.sample_period = + intel_pt_ns_to_ticks(pt, pt->synth_opts.period); + else + attr.sample_period = pt->synth_opts.period; + err = intel_pt_synth_event(session, "cycles", &attr, id); + if (err) + return err; + pt->sample_cycles = true; + pt->cycles_sample_type = attr.sample_type; + pt->cycles_id = id; + id += 1; + } + attr.sample_type &= ~(u64)PERF_SAMPLE_PERIOD; attr.sample_period = 1; @@ -4379,6 +4436,12 @@ int intel_pt_process_auxtrace_info(union perf_event *event, intel_pt_setup_pebs_events(pt); + if (perf_data__is_pipe(session->data)) { + pr_warning("WARNING: Intel PT with pipe mode is not recommended.\n" + " The output cannot relied upon. In particular,\n" + " timestamps and the order of events may be incorrect.\n"); + } + if (pt->sampling_mode || list_empty(&session->auxtrace_index)) err = auxtrace_queue_data(session, true, true); else diff --git a/tools/perf/util/llvm-utils.c b/tools/perf/util/llvm-utils.c index 650ffe336f3a..4e8e243a6e4b 100644 --- a/tools/perf/util/llvm-utils.c +++ b/tools/perf/util/llvm-utils.c @@ -531,14 +531,37 @@ int llvm__compile_bpf(const char *path, void **p_obj_buf, pr_debug("llvm compiling command template: %s\n", template); + /* + * Below, substitute control characters for values that can cause the + * echo to misbehave, then substitute the values back. + */ err = -ENOMEM; - if (asprintf(&command_echo, "echo -n \"%s\"", template) < 0) + if (asprintf(&command_echo, "echo -n \a%s\a", template) < 0) goto errout; +#define SWAP_CHAR(a, b) do { if (*p == a) *p = b; } while (0) + for (char *p = command_echo; *p; p++) { + SWAP_CHAR('<', '\001'); + SWAP_CHAR('>', '\002'); + SWAP_CHAR('"', '\003'); + SWAP_CHAR('\'', '\004'); + SWAP_CHAR('|', '\005'); + SWAP_CHAR('&', '\006'); + SWAP_CHAR('\a', '"'); + } err = read_from_pipe(command_echo, (void **) &command_out, NULL); if (err) goto errout; + for (char *p = command_out; *p; p++) { + SWAP_CHAR('\001', '<'); + SWAP_CHAR('\002', '>'); + SWAP_CHAR('\003', '"'); + SWAP_CHAR('\004', '\''); + SWAP_CHAR('\005', '|'); + SWAP_CHAR('\006', '&'); + } +#undef SWAP_CHAR pr_debug("llvm compiling command : %s\n", command_out); err = read_from_pipe(template, &obj_buf, &obj_buf_sz); diff --git a/tools/perf/util/lock-contention.h b/tools/perf/util/lock-contention.h index b99e83fccf5c..040b618b2215 100644 --- a/tools/perf/util/lock-contention.h +++ b/tools/perf/util/lock-contention.h @@ -65,6 +65,11 @@ struct lock_stat { */ #define MAX_LOCK_DEPTH 48 +struct lock_stat *lock_stat_find(u64 addr); +struct lock_stat *lock_stat_findnew(u64 addr, const char *name, int flags); + +bool match_callstack_filter(struct machine *machine, u64 *callstack); + /* * struct lock_seq_stat: * Place to put on state of one lock sequence @@ -128,6 +133,8 @@ struct lock_contention { int max_stack; int stack_skip; int aggr_mode; + int owner; + bool save_callstack; }; #ifdef HAVE_BPF_SKEL diff --git a/tools/perf/util/metricgroup.c b/tools/perf/util/metricgroup.c index b9c273ed080a..f3559be95541 100644 --- a/tools/perf/util/metricgroup.c +++ b/tools/perf/util/metricgroup.c @@ -167,14 +167,14 @@ static void metricgroup___watchdog_constraint_hint(const char *name, bool foot) " echo 1 > /proc/sys/kernel/nmi_watchdog\n"); } -static bool metricgroup__has_constraint(const struct pmu_event *pe) +static bool metricgroup__has_constraint(const struct pmu_metric *pm) { - if (!pe->metric_constraint) + if (!pm->metric_constraint) return false; - if (!strcmp(pe->metric_constraint, "NO_NMI_WATCHDOG") && + if (!strcmp(pm->metric_constraint, "NO_NMI_WATCHDOG") && sysctl__nmi_watchdog_enabled()) { - metricgroup___watchdog_constraint_hint(pe->metric_name, false); + metricgroup___watchdog_constraint_hint(pm->metric_name, false); return true; } @@ -193,7 +193,7 @@ static void metric__free(struct metric *m) free(m); } -static struct metric *metric__new(const struct pmu_event *pe, +static struct metric *metric__new(const struct pmu_metric *pm, const char *modifier, bool metric_no_group, int runtime, @@ -210,15 +210,15 @@ static struct metric *metric__new(const struct pmu_event *pe, if (!m->pctx) goto out_err; - m->metric_name = pe->metric_name; + m->metric_name = pm->metric_name; m->modifier = NULL; if (modifier) { m->modifier = strdup(modifier); if (!m->modifier) goto out_err; } - m->metric_expr = pe->metric_expr; - m->metric_unit = pe->unit; + m->metric_expr = pm->metric_expr; + m->metric_unit = pm->unit; m->pctx->sctx.user_requested_cpu_list = NULL; if (user_requested_cpu_list) { m->pctx->sctx.user_requested_cpu_list = strdup(user_requested_cpu_list); @@ -227,7 +227,7 @@ static struct metric *metric__new(const struct pmu_event *pe, } m->pctx->sctx.runtime = runtime; m->pctx->sctx.system_wide = system_wide; - m->has_constraint = metric_no_group || metricgroup__has_constraint(pe); + m->has_constraint = metric_no_group || metricgroup__has_constraint(pm); m->metric_refs = NULL; m->evlist = NULL; @@ -348,10 +348,10 @@ static bool match_metric(const char *n, const char *list) return false; } -static bool match_pe_metric(const struct pmu_event *pe, const char *metric) +static bool match_pm_metric(const struct pmu_metric *pm, const char *metric) { - return match_metric(pe->metric_group, metric) || - match_metric(pe->metric_name, metric); + return match_metric(pm->metric_group, metric) || + match_metric(pm->metric_name, metric); } /** struct mep - RB-tree node for building printing information. */ @@ -420,13 +420,13 @@ static struct mep *mep_lookup(struct rblist *groups, const char *metric_group, return NULL; } -static int metricgroup__add_to_mep_groups(const struct pmu_event *pe, +static int metricgroup__add_to_mep_groups(const struct pmu_metric *pm, struct rblist *groups) { const char *g; char *omg, *mg; - mg = strdup(pe->metric_group ?: "No_group"); + mg = strdup(pm->metric_group ?: "No_group"); if (!mg) return -ENOMEM; omg = mg; @@ -435,15 +435,15 @@ static int metricgroup__add_to_mep_groups(const struct pmu_event *pe, g = skip_spaces(g); if (strlen(g)) - me = mep_lookup(groups, g, pe->metric_name); + me = mep_lookup(groups, g, pm->metric_name); else - me = mep_lookup(groups, "No_group", pe->metric_name); + me = mep_lookup(groups, "No_group", pm->metric_name); if (me) { - me->metric_desc = pe->desc; - me->metric_long_desc = pe->long_desc; - me->metric_expr = pe->metric_expr; - me->metric_unit = pe->unit; + me->metric_desc = pm->desc; + me->metric_long_desc = pm->long_desc; + me->metric_expr = pm->metric_expr; + me->metric_unit = pm->unit; } } free(omg); @@ -452,64 +452,61 @@ static int metricgroup__add_to_mep_groups(const struct pmu_event *pe, } struct metricgroup_iter_data { - pmu_event_iter_fn fn; + pmu_metric_iter_fn fn; void *data; }; -static int metricgroup__sys_event_iter(const struct pmu_event *pe, - const struct pmu_events_table *table, +static int metricgroup__sys_event_iter(const struct pmu_metric *pm, + const struct pmu_metrics_table *table, void *data) { struct metricgroup_iter_data *d = data; struct perf_pmu *pmu = NULL; - if (!pe->metric_expr || !pe->compat) + if (!pm->metric_expr || !pm->compat) return 0; while ((pmu = perf_pmu__scan(pmu))) { - if (!pmu->id || strcmp(pmu->id, pe->compat)) + if (!pmu->id || strcmp(pmu->id, pm->compat)) continue; - return d->fn(pe, table, d->data); + return d->fn(pm, table, d->data); } return 0; } -static int metricgroup__add_to_mep_groups_callback(const struct pmu_event *pe, - const struct pmu_events_table *table __maybe_unused, - void *vdata) +static int metricgroup__add_to_mep_groups_callback(const struct pmu_metric *pm, + const struct pmu_metrics_table *table __maybe_unused, + void *vdata) { struct rblist *groups = vdata; - if (!pe->metric_name) - return 0; - - return metricgroup__add_to_mep_groups(pe, groups); + return metricgroup__add_to_mep_groups(pm, groups); } void metricgroup__print(const struct print_callbacks *print_cb, void *print_state) { struct rblist groups; - const struct pmu_events_table *table; + const struct pmu_metrics_table *table; struct rb_node *node, *next; rblist__init(&groups); groups.node_new = mep_new; groups.node_cmp = mep_cmp; groups.node_delete = mep_delete; - table = pmu_events_table__find(); + table = pmu_metrics_table__find(); if (table) { - pmu_events_table_for_each_event(table, - metricgroup__add_to_mep_groups_callback, - &groups); + pmu_metrics_table_for_each_metric(table, + metricgroup__add_to_mep_groups_callback, + &groups); } { struct metricgroup_iter_data data = { .fn = metricgroup__add_to_mep_groups_callback, .data = &groups, }; - pmu_for_each_sys_event(metricgroup__sys_event_iter, &data); + pmu_for_each_sys_metric(metricgroup__sys_event_iter, &data); } for (node = rb_first_cached(&groups.entries); node; node = next) { @@ -743,7 +740,7 @@ static int metricgroup__build_event_string(struct strbuf *events, #undef RETURN_IF_NON_ZERO } -int __weak arch_get_runtimeparam(const struct pmu_event *pe __maybe_unused) +int __weak arch_get_runtimeparam(const struct pmu_metric *pm __maybe_unused) { return 1; } @@ -768,22 +765,22 @@ struct metricgroup_add_iter_data { bool system_wide; struct metric *root_metric; const struct visited_metric *visited; - const struct pmu_events_table *table; + const struct pmu_metrics_table *table; }; static bool metricgroup__find_metric(const char *metric, - const struct pmu_events_table *table, - struct pmu_event *pe); + const struct pmu_metrics_table *table, + struct pmu_metric *pm); static int add_metric(struct list_head *metric_list, - const struct pmu_event *pe, + const struct pmu_metric *pm, const char *modifier, bool metric_no_group, const char *user_requested_cpu_list, bool system_wide, struct metric *root_metric, const struct visited_metric *visited, - const struct pmu_events_table *table); + const struct pmu_metrics_table *table); /** * resolve_metric - Locate metrics within the root metric and recursively add @@ -810,16 +807,16 @@ static int resolve_metric(struct list_head *metric_list, bool system_wide, struct metric *root_metric, const struct visited_metric *visited, - const struct pmu_events_table *table) + const struct pmu_metrics_table *table) { struct hashmap_entry *cur; size_t bkt; struct to_resolve { /* The metric to resolve. */ - struct pmu_event pe; + struct pmu_metric pm; /* * The key in the IDs map, this may differ from in case, - * etc. from pe->metric_name. + * etc. from pm->metric_name. */ const char *key; } *pending = NULL; @@ -830,15 +827,15 @@ static int resolve_metric(struct list_head *metric_list, * the pending array. */ hashmap__for_each_entry(root_metric->pctx->ids, cur, bkt) { - struct pmu_event pe; + struct pmu_metric pm; - if (metricgroup__find_metric(cur->pkey, table, &pe)) { + if (metricgroup__find_metric(cur->pkey, table, &pm)) { pending = realloc(pending, (pending_cnt + 1) * sizeof(struct to_resolve)); if (!pending) return -ENOMEM; - memcpy(&pending[pending_cnt].pe, &pe, sizeof(pe)); + memcpy(&pending[pending_cnt].pm, &pm, sizeof(pm)); pending[pending_cnt].key = cur->pkey; pending_cnt++; } @@ -853,7 +850,7 @@ static int resolve_metric(struct list_head *metric_list, * context. */ for (i = 0; i < pending_cnt; i++) { - ret = add_metric(metric_list, &pending[i].pe, modifier, metric_no_group, + ret = add_metric(metric_list, &pending[i].pm, modifier, metric_no_group, user_requested_cpu_list, system_wide, root_metric, visited, table); if (ret) @@ -867,7 +864,7 @@ static int resolve_metric(struct list_head *metric_list, /** * __add_metric - Add a metric to metric_list. * @metric_list: The list the metric is added to. - * @pe: The pmu_event containing the metric to be added. + * @pm: The pmu_metric containing the metric to be added. * @modifier: if non-null event modifiers like "u". * @metric_no_group: Should events written to events be grouped "{}" or * global. Grouping is the default but due to multiplexing the @@ -884,7 +881,7 @@ static int resolve_metric(struct list_head *metric_list, * architecture perf is running upon. */ static int __add_metric(struct list_head *metric_list, - const struct pmu_event *pe, + const struct pmu_metric *pm, const char *modifier, bool metric_no_group, int runtime, @@ -892,19 +889,19 @@ static int __add_metric(struct list_head *metric_list, bool system_wide, struct metric *root_metric, const struct visited_metric *visited, - const struct pmu_events_table *table) + const struct pmu_metrics_table *table) { const struct visited_metric *vm; int ret; bool is_root = !root_metric; struct visited_metric visited_node = { - .name = pe->metric_name, + .name = pm->metric_name, .parent = visited, }; for (vm = visited; vm; vm = vm->parent) { - if (!strcmp(pe->metric_name, vm->name)) { - pr_err("failed: recursion detected for %s\n", pe->metric_name); + if (!strcmp(pm->metric_name, vm->name)) { + pr_err("failed: recursion detected for %s\n", pm->metric_name); return -1; } } @@ -914,7 +911,7 @@ static int __add_metric(struct list_head *metric_list, * This metric is the root of a tree and may reference other * metrics that are added recursively. */ - root_metric = metric__new(pe, modifier, metric_no_group, runtime, + root_metric = metric__new(pm, modifier, metric_no_group, runtime, user_requested_cpu_list, system_wide); if (!root_metric) return -ENOMEM; @@ -929,7 +926,7 @@ static int __add_metric(struct list_head *metric_list, */ if (root_metric->metric_refs) { for (; root_metric->metric_refs[cnt].metric_name; cnt++) { - if (!strcmp(pe->metric_name, + if (!strcmp(pm->metric_name, root_metric->metric_refs[cnt].metric_name)) return 0; } @@ -947,8 +944,8 @@ static int __add_metric(struct list_head *metric_list, * need to change them, so there's no need to create * our own copy. */ - root_metric->metric_refs[cnt].metric_name = pe->metric_name; - root_metric->metric_refs[cnt].metric_expr = pe->metric_expr; + root_metric->metric_refs[cnt].metric_name = pm->metric_name; + root_metric->metric_refs[cnt].metric_expr = pm->metric_expr; /* Null terminate array. */ root_metric->metric_refs[cnt+1].metric_name = NULL; @@ -959,7 +956,7 @@ static int __add_metric(struct list_head *metric_list, * For both the parent and referenced metrics, we parse * all the metric's IDs and add it to the root context. */ - if (expr__find_ids(pe->metric_expr, NULL, root_metric->pctx) < 0) { + if (expr__find_ids(pm->metric_expr, NULL, root_metric->pctx) < 0) { /* Broken metric. */ ret = -EINVAL; } else { @@ -981,57 +978,57 @@ static int __add_metric(struct list_head *metric_list, struct metricgroup__find_metric_data { const char *metric; - struct pmu_event *pe; + struct pmu_metric *pm; }; -static int metricgroup__find_metric_callback(const struct pmu_event *pe, - const struct pmu_events_table *table __maybe_unused, +static int metricgroup__find_metric_callback(const struct pmu_metric *pm, + const struct pmu_metrics_table *table __maybe_unused, void *vdata) { struct metricgroup__find_metric_data *data = vdata; - if (!match_metric(pe->metric_name, data->metric)) + if (!match_metric(pm->metric_name, data->metric)) return 0; - memcpy(data->pe, pe, sizeof(*pe)); + memcpy(data->pm, pm, sizeof(*pm)); return 1; } static bool metricgroup__find_metric(const char *metric, - const struct pmu_events_table *table, - struct pmu_event *pe) + const struct pmu_metrics_table *table, + struct pmu_metric *pm) { struct metricgroup__find_metric_data data = { .metric = metric, - .pe = pe, + .pm = pm, }; - return pmu_events_table_for_each_event(table, metricgroup__find_metric_callback, &data) + return pmu_metrics_table_for_each_metric(table, metricgroup__find_metric_callback, &data) ? true : false; } static int add_metric(struct list_head *metric_list, - const struct pmu_event *pe, + const struct pmu_metric *pm, const char *modifier, bool metric_no_group, const char *user_requested_cpu_list, bool system_wide, struct metric *root_metric, const struct visited_metric *visited, - const struct pmu_events_table *table) + const struct pmu_metrics_table *table) { int ret = 0; - pr_debug("metric expr %s for %s\n", pe->metric_expr, pe->metric_name); + pr_debug("metric expr %s for %s\n", pm->metric_expr, pm->metric_name); - if (!strstr(pe->metric_expr, "?")) { - ret = __add_metric(metric_list, pe, modifier, metric_no_group, 0, + if (!strstr(pm->metric_expr, "?")) { + ret = __add_metric(metric_list, pm, modifier, metric_no_group, 0, user_requested_cpu_list, system_wide, root_metric, visited, table); } else { int j, count; - count = arch_get_runtimeparam(pe); + count = arch_get_runtimeparam(pm); /* This loop is added to create multiple * events depend on count value and add @@ -1039,7 +1036,7 @@ static int add_metric(struct list_head *metric_list, */ for (j = 0; j < count && !ret; j++) - ret = __add_metric(metric_list, pe, modifier, metric_no_group, j, + ret = __add_metric(metric_list, pm, modifier, metric_no_group, j, user_requested_cpu_list, system_wide, root_metric, visited, table); } @@ -1047,17 +1044,17 @@ static int add_metric(struct list_head *metric_list, return ret; } -static int metricgroup__add_metric_sys_event_iter(const struct pmu_event *pe, - const struct pmu_events_table *table __maybe_unused, - void *data) +static int metricgroup__add_metric_sys_event_iter(const struct pmu_metric *pm, + const struct pmu_metrics_table *table __maybe_unused, + void *data) { struct metricgroup_add_iter_data *d = data; int ret; - if (!match_pe_metric(pe, d->metric_name)) + if (!match_pm_metric(pm, d->metric_name)) return 0; - ret = add_metric(d->metric_list, pe, d->modifier, d->metric_no_group, + ret = add_metric(d->metric_list, pm, d->modifier, d->metric_no_group, d->user_requested_cpu_list, d->system_wide, d->root_metric, d->visited, d->table); if (ret) @@ -1107,19 +1104,19 @@ struct metricgroup__add_metric_data { bool has_match; }; -static int metricgroup__add_metric_callback(const struct pmu_event *pe, - const struct pmu_events_table *table, +static int metricgroup__add_metric_callback(const struct pmu_metric *pm, + const struct pmu_metrics_table *table, void *vdata) { struct metricgroup__add_metric_data *data = vdata; int ret = 0; - if (pe->metric_expr && - (match_metric(pe->metric_group, data->metric_name) || - match_metric(pe->metric_name, data->metric_name))) { + if (pm->metric_expr && + (match_metric(pm->metric_group, data->metric_name) || + match_metric(pm->metric_name, data->metric_name))) { data->has_match = true; - ret = add_metric(data->list, pe, data->modifier, data->metric_no_group, + ret = add_metric(data->list, pm, data->modifier, data->metric_no_group, data->user_requested_cpu_list, data->system_wide, /*root_metric=*/NULL, /*visited_metrics=*/NULL, table); } @@ -1146,7 +1143,7 @@ static int metricgroup__add_metric(const char *metric_name, const char *modifier const char *user_requested_cpu_list, bool system_wide, struct list_head *metric_list, - const struct pmu_events_table *table) + const struct pmu_metrics_table *table) { LIST_HEAD(list); int ret; @@ -1166,8 +1163,8 @@ static int metricgroup__add_metric(const char *metric_name, const char *modifier * Iterate over all metrics seeing if metric matches either the * name or group. When it does add the metric to the list. */ - ret = pmu_events_table_for_each_event(table, metricgroup__add_metric_callback, - &data); + ret = pmu_metrics_table_for_each_metric(table, metricgroup__add_metric_callback, + &data); if (ret) goto out; @@ -1189,7 +1186,7 @@ static int metricgroup__add_metric(const char *metric_name, const char *modifier }, }; - pmu_for_each_sys_event(metricgroup__sys_event_iter, &data); + pmu_for_each_sys_metric(metricgroup__sys_event_iter, &data); } /* End of pmu events. */ if (!has_match) @@ -1222,7 +1219,7 @@ out: static int metricgroup__add_metric_list(const char *list, bool metric_no_group, const char *user_requested_cpu_list, bool system_wide, struct list_head *metric_list, - const struct pmu_events_table *table) + const struct pmu_metrics_table *table) { char *list_itr, *list_copy, *metric_name, *modifier; int ret, count = 0; @@ -1432,7 +1429,7 @@ static int parse_groups(struct evlist *perf_evlist, const char *str, bool system_wide, struct perf_pmu *fake_pmu, struct rblist *metric_events_list, - const struct pmu_events_table *table) + const struct pmu_metrics_table *table) { struct evlist *combined_evlist = NULL; LIST_HEAD(metric_list); @@ -1580,7 +1577,7 @@ int metricgroup__parse_groups(struct evlist *perf_evlist, bool system_wide, struct rblist *metric_events) { - const struct pmu_events_table *table = pmu_events_table__find(); + const struct pmu_metrics_table *table = pmu_metrics_table__find(); if (!table) return -EINVAL; @@ -1591,7 +1588,7 @@ int metricgroup__parse_groups(struct evlist *perf_evlist, } int metricgroup__parse_groups_test(struct evlist *evlist, - const struct pmu_events_table *table, + const struct pmu_metrics_table *table, const char *str, bool metric_no_group, bool metric_no_merge, @@ -1603,16 +1600,16 @@ int metricgroup__parse_groups_test(struct evlist *evlist, &perf_pmu__fake, metric_events, table); } -static int metricgroup__has_metric_callback(const struct pmu_event *pe, - const struct pmu_events_table *table __maybe_unused, +static int metricgroup__has_metric_callback(const struct pmu_metric *pm, + const struct pmu_metrics_table *table __maybe_unused, void *vdata) { const char *metric = vdata; - if (!pe->metric_expr) + if (!pm->metric_expr) return 0; - if (match_metric(pe->metric_name, metric)) + if (match_metric(pm->metric_name, metric)) return 1; return 0; @@ -1620,13 +1617,13 @@ static int metricgroup__has_metric_callback(const struct pmu_event *pe, bool metricgroup__has_metric(const char *metric) { - const struct pmu_events_table *table = pmu_events_table__find(); + const struct pmu_metrics_table *table = pmu_metrics_table__find(); if (!table) return false; - return pmu_events_table_for_each_event(table, metricgroup__has_metric_callback, - (void *)metric) ? true : false; + return pmu_metrics_table_for_each_metric(table, metricgroup__has_metric_callback, + (void *)metric) ? true : false; } int metricgroup__copy_metric_events(struct evlist *evlist, struct cgroup *cgrp, diff --git a/tools/perf/util/metricgroup.h b/tools/perf/util/metricgroup.h index 0013cf582173..84030321a057 100644 --- a/tools/perf/util/metricgroup.h +++ b/tools/perf/util/metricgroup.h @@ -73,7 +73,7 @@ int metricgroup__parse_groups(struct evlist *perf_evlist, bool system_wide, struct rblist *metric_events); int metricgroup__parse_groups_test(struct evlist *evlist, - const struct pmu_events_table *table, + const struct pmu_metrics_table *table, const char *str, bool metric_no_group, bool metric_no_merge, @@ -81,7 +81,7 @@ int metricgroup__parse_groups_test(struct evlist *evlist, void metricgroup__print(const struct print_callbacks *print_cb, void *print_state); bool metricgroup__has_metric(const char *metric); -int arch_get_runtimeparam(const struct pmu_event *pe __maybe_unused); +int arch_get_runtimeparam(const struct pmu_metric *pm); void metricgroup__rblist_exit(struct rblist *metric_events); int metricgroup__copy_metric_events(struct evlist *evlist, struct cgroup *cgrp, diff --git a/tools/perf/util/parse-events.c b/tools/perf/util/parse-events.c index 21cce83462b3..0336ff27c15f 100644 --- a/tools/perf/util/parse-events.c +++ b/tools/perf/util/parse-events.c @@ -1570,8 +1570,6 @@ int parse_events_add_pmu(struct parse_events_state *parse_state, evsel->scale = info.scale; evsel->per_pkg = info.per_pkg; evsel->snapshot = info.snapshot; - evsel->metric_expr = info.metric_expr; - evsel->metric_name = info.metric_name; return 0; } diff --git a/tools/perf/util/pfm.c b/tools/perf/util/pfm.c index ac3227ba769c..b59ba825ddc9 100644 --- a/tools/perf/util/pfm.c +++ b/tools/perf/util/pfm.c @@ -193,8 +193,7 @@ print_libpfm_event(const struct print_callbacks *print_cb, void *print_state, /*scale_unit=*/NULL, /*deprecated=*/NULL, "PFM event", info->desc, /*long_desc=*/NULL, - /*encoding_desc=*/buf->buf, - /*metric_name=*/NULL, /*metric_expr=*/NULL); + /*encoding_desc=*/buf->buf); pfm_for_each_event_attr(j, info) { pfm_event_attr_info_t ainfo; @@ -224,8 +223,7 @@ print_libpfm_event(const struct print_callbacks *print_cb, void *print_state, /*scale_unit=*/NULL, /*deprecated=*/NULL, "PFM event", ainfo.desc, /*long_desc=*/NULL, - /*encoding_desc=*/buf->buf, - /*metric_name=*/NULL, /*metric_expr=*/NULL); + /*encoding_desc=*/buf->buf); } } } diff --git a/tools/perf/util/pmu-hybrid.c b/tools/perf/util/pmu-hybrid.c index f51ccaac60ee..38628805a952 100644 --- a/tools/perf/util/pmu-hybrid.c +++ b/tools/perf/util/pmu-hybrid.c @@ -20,32 +20,15 @@ LIST_HEAD(perf_pmu__hybrid_pmus); bool perf_pmu__hybrid_mounted(const char *name) { - char path[PATH_MAX]; - const char *sysfs; - FILE *file; - int n, cpu; + int cpu; + char pmu_name[PATH_MAX]; + struct perf_pmu pmu = {.name = pmu_name}; if (strncmp(name, "cpu_", 4)) return false; - sysfs = sysfs__mountpoint(); - if (!sysfs) - return false; - - snprintf(path, PATH_MAX, CPUS_TEMPLATE_CPU, sysfs, name); - if (!file_available(path)) - return false; - - file = fopen(path, "r"); - if (!file) - return false; - - n = fscanf(file, "%u", &cpu); - fclose(file); - if (n <= 0) - return false; - - return true; + strlcpy(pmu_name, name, sizeof(pmu_name)); + return perf_pmu__scan_file(&pmu, "cpus", "%u", &cpu) > 0; } struct perf_pmu *perf_pmu__find_hybrid_pmu(const char *name) diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c index 2bdeb89352e7..c256b29defad 100644 --- a/tools/perf/util/pmu.c +++ b/tools/perf/util/pmu.c @@ -19,6 +19,7 @@ #include <regex.h> #include <perf/cpumap.h> #include <fnmatch.h> +#include <math.h> #include "debug.h" #include "evsel.h" #include "pmu.h" @@ -107,14 +108,10 @@ int perf_pmu__format_parse(char *dir, struct list_head *head) static int pmu_format(const char *name, struct list_head *format) { char path[PATH_MAX]; - const char *sysfs = sysfs__mountpoint(); - if (!sysfs) + if (!perf_pmu__pathname_scnprintf(path, sizeof(path), name, "format")) return -1; - snprintf(path, PATH_MAX, - "%s" EVENT_SOURCE_DEVICE_PATH "%s/format", sysfs, name); - if (!file_available(path)) return 0; @@ -283,10 +280,6 @@ static void perf_pmu_update_alias(struct perf_pmu_alias *old, perf_pmu_assign_str(old->name, "long_desc", &old->long_desc, &newalias->long_desc); perf_pmu_assign_str(old->name, "topic", &old->topic, &newalias->topic); - perf_pmu_assign_str(old->name, "metric_expr", &old->metric_expr, - &newalias->metric_expr); - perf_pmu_assign_str(old->name, "metric_name", &old->metric_name, - &newalias->metric_name); perf_pmu_assign_str(old->name, "value", &old->str, &newalias->str); old->scale = newalias->scale; old->per_pkg = newalias->per_pkg; @@ -302,8 +295,6 @@ void perf_pmu_free_alias(struct perf_pmu_alias *newalias) zfree(&newalias->long_desc); zfree(&newalias->topic); zfree(&newalias->str); - zfree(&newalias->metric_expr); - zfree(&newalias->metric_name); zfree(&newalias->pmu_name); parse_events_terms__purge(&newalias->terms); free(newalias); @@ -340,16 +331,13 @@ static int __perf_pmu__new_alias(struct list_head *list, char *dir, char *name, int num; char newval[256]; char *long_desc = NULL, *topic = NULL, *unit = NULL, *perpkg = NULL, - *metric_expr = NULL, *metric_name = NULL, *deprecated = NULL, - *pmu_name = NULL; + *deprecated = NULL, *pmu_name = NULL; if (pe) { long_desc = (char *)pe->long_desc; topic = (char *)pe->topic; unit = (char *)pe->unit; perpkg = (char *)pe->perpkg; - metric_expr = (char *)pe->metric_expr; - metric_name = (char *)pe->metric_name; deprecated = (char *)pe->deprecated; pmu_name = (char *)pe->pmu; } @@ -404,8 +392,6 @@ static int __perf_pmu__new_alias(struct list_head *list, char *dir, char *name, perf_pmu__parse_snapshot(alias, dir, name); } - alias->metric_expr = metric_expr ? strdup(metric_expr) : NULL; - alias->metric_name = metric_name ? strdup(metric_name): NULL; alias->desc = desc ? strdup(desc) : NULL; alias->long_desc = long_desc ? strdup(long_desc) : desc ? strdup(desc) : NULL; @@ -513,14 +499,10 @@ static int pmu_aliases_parse(char *dir, struct list_head *head) static int pmu_aliases(const char *name, struct list_head *head) { char path[PATH_MAX]; - const char *sysfs = sysfs__mountpoint(); - if (!sysfs) + if (!perf_pmu__pathname_scnprintf(path, sizeof(path), name, "events")) return -1; - snprintf(path, PATH_MAX, - "%s/bus/event_source/devices/%s/events", sysfs, name); - if (!file_available(path)) return 0; @@ -554,52 +536,16 @@ static int pmu_alias_terms(struct perf_pmu_alias *alias, return 0; } -/* - * Reading/parsing the default pmu type value, which should be - * located at: - * /sys/bus/event_source/devices/<dev>/type as sysfs attribute. - */ -static int pmu_type(const char *name, __u32 *type) -{ - char path[PATH_MAX]; - FILE *file; - int ret = 0; - const char *sysfs = sysfs__mountpoint(); - - if (!sysfs) - return -1; - - snprintf(path, PATH_MAX, - "%s" EVENT_SOURCE_DEVICE_PATH "%s/type", sysfs, name); - - if (access(path, R_OK) < 0) - return -1; - - file = fopen(path, "r"); - if (!file) - return -EINVAL; - - if (1 != fscanf(file, "%u", type)) - ret = -1; - - fclose(file); - return ret; -} - /* Add all pmus in sysfs to pmu list: */ static void pmu_read_sysfs(void) { char path[PATH_MAX]; DIR *dir; struct dirent *dent; - const char *sysfs = sysfs__mountpoint(); - if (!sysfs) + if (!perf_pmu__event_source_devices_scnprintf(path, sizeof(path))) return; - snprintf(path, PATH_MAX, - "%s" EVENT_SOURCE_DEVICE_PATH, sysfs); - dir = opendir(path); if (!dir) return; @@ -614,45 +560,29 @@ static void pmu_read_sysfs(void) closedir(dir); } -static struct perf_cpu_map *__pmu_cpumask(const char *path) -{ - FILE *file; - struct perf_cpu_map *cpus; - - file = fopen(path, "r"); - if (!file) - return NULL; - - cpus = perf_cpu_map__read(file); - fclose(file); - return cpus; -} - /* * Uncore PMUs have a "cpumask" file under sysfs. CPU PMUs (e.g. on arm/arm64) * may have a "cpus" file. */ -#define SYS_TEMPLATE_ID "./bus/event_source/devices/%s/identifier" -#define CPUS_TEMPLATE_UNCORE "%s/bus/event_source/devices/%s/cpumask" - static struct perf_cpu_map *pmu_cpumask(const char *name) { - char path[PATH_MAX]; struct perf_cpu_map *cpus; - const char *sysfs = sysfs__mountpoint(); const char *templates[] = { - CPUS_TEMPLATE_UNCORE, - CPUS_TEMPLATE_CPU, + "cpumask", + "cpus", NULL }; const char **template; + char pmu_name[PATH_MAX]; + struct perf_pmu pmu = {.name = pmu_name}; + FILE *file; - if (!sysfs) - return NULL; - + strlcpy(pmu_name, name, sizeof(pmu_name)); for (template = templates; *template; template++) { - snprintf(path, PATH_MAX, *template, sysfs, name); - cpus = __pmu_cpumask(path); + file = perf_pmu__open_file(&pmu, *template); + if (!file) + continue; + cpus = perf_cpu_map__read(file); if (cpus) return cpus; } @@ -663,13 +593,11 @@ static struct perf_cpu_map *pmu_cpumask(const char *name) static bool pmu_is_uncore(const char *name) { char path[PATH_MAX]; - const char *sysfs; if (perf_pmu__hybrid_mounted(name)) return false; - sysfs = sysfs__mountpoint(); - snprintf(path, PATH_MAX, CPUS_TEMPLATE_UNCORE, sysfs, name); + perf_pmu__pathname_scnprintf(path, sizeof(path), name, "cpumask"); return file_available(path); } @@ -678,9 +606,9 @@ static char *pmu_id(const char *name) char path[PATH_MAX], *str; size_t len; - snprintf(path, PATH_MAX, SYS_TEMPLATE_ID, name); + perf_pmu__pathname_scnprintf(path, sizeof(path), name, "identifier"); - if (sysfs__read_str(path, &str, &len) < 0) + if (filename__read_str(path, &str, &len) < 0) return NULL; str[len - 1] = 0; /* remove line feed */ @@ -696,14 +624,9 @@ static char *pmu_id(const char *name) static int is_arm_pmu_core(const char *name) { char path[PATH_MAX]; - const char *sysfs = sysfs__mountpoint(); - if (!sysfs) + if (!perf_pmu__pathname_scnprintf(path, sizeof(path), name, "cpus")) return 0; - - /* Look for cpu sysfs (specific to arm) */ - scnprintf(path, PATH_MAX, "%s/bus/event_source/devices/%s/cpus", - sysfs, name); return file_available(path); } @@ -729,7 +652,12 @@ char *perf_pmu__getcpuid(struct perf_pmu *pmu) __weak const struct pmu_events_table *pmu_events_table__find(void) { - return perf_pmu__find_table(NULL); + return perf_pmu__find_events_table(NULL); +} + +__weak const struct pmu_metrics_table *pmu_metrics_table__find(void) +{ + return perf_pmu__find_metrics_table(NULL); } /* @@ -822,9 +750,6 @@ static int pmu_add_cpu_aliases_map_callback(const struct pmu_event *pe, struct pmu_add_cpu_aliases_map_data *data = vdata; const char *pname = pe->pmu ? pe->pmu : data->cpu_name; - if (!pe->name) - return 0; - if (data->pmu->is_uncore && pmu_uncore_alias_match(pname, data->name)) goto new_alias; @@ -860,7 +785,7 @@ static void pmu_add_cpu_aliases(struct list_head *head, struct perf_pmu *pmu) { const struct pmu_events_table *table; - table = perf_pmu__find_table(pmu); + table = perf_pmu__find_events_table(pmu); if (!table) return; @@ -879,12 +804,6 @@ static int pmu_add_sys_aliases_iter_fn(const struct pmu_event *pe, struct pmu_sys_event_iter_data *idata = data; struct perf_pmu *pmu = idata->pmu; - if (!pe->name) { - if (pe->metric_group || pe->metric_name) - return 0; - return -EINVAL; - } - if (!pe->compat || !pe->pmu) return 0; @@ -931,16 +850,11 @@ pmu_find_alias_name(const char *name __maybe_unused) return NULL; } -static int pmu_max_precise(const char *name) +static int pmu_max_precise(struct perf_pmu *pmu) { - char path[PATH_MAX]; int max_precise = -1; - scnprintf(path, PATH_MAX, - "bus/event_source/devices/%s/caps/max_precise", - name); - - sysfs__read_int(path, &max_precise); + perf_pmu__scan_file(pmu, "caps/max_precise", "%d", &max_precise); return max_precise; } @@ -969,11 +883,8 @@ static struct perf_pmu *pmu_lookup(const char *lookup_name) return NULL; /* - * Check the type first to avoid unnecessary work. + * Check the aliases first to avoid unnecessary work. */ - if (pmu_type(name, &type)) - return NULL; - if (pmu_aliases(name, &aliases)) return NULL; @@ -983,9 +894,14 @@ static struct perf_pmu *pmu_lookup(const char *lookup_name) pmu->cpus = pmu_cpumask(name); pmu->name = strdup(name); + if (!pmu->name) goto err; + /* Read type, and ensure that type value is successfully assigned (return 1) */ + if (perf_pmu__scan_file(pmu, "type", "%u", &type) != 1) + goto err; + alias_name = pmu_find_alias_name(name); if (alias_name) { pmu->alias_name = strdup(alias_name); @@ -997,7 +913,7 @@ static struct perf_pmu *pmu_lookup(const char *lookup_name) pmu->is_uncore = pmu_is_uncore(name); if (pmu->is_uncore) pmu->id = pmu_id(name); - pmu->max_precise = pmu_max_precise(name); + pmu->max_precise = pmu_max_precise(pmu); pmu_add_cpu_aliases(&aliases, pmu); pmu_add_sys_aliases(&aliases, pmu); @@ -1469,8 +1385,6 @@ int perf_pmu__check_alias(struct perf_pmu *pmu, struct list_head *head_terms, info->unit = NULL; info->scale = 0.0; info->snapshot = false; - info->metric_expr = NULL; - info->metric_name = NULL; list_for_each_entry_safe(term, h, head_terms, list) { alias = pmu_find_alias(pmu, term); @@ -1486,8 +1400,6 @@ int perf_pmu__check_alias(struct perf_pmu *pmu, struct list_head *head_terms, if (alias->per_pkg) info->per_pkg = true; - info->metric_expr = alias->metric_expr; - info->metric_name = alias->metric_name; list_del_init(&term->list); parse_events_term__delete(term); @@ -1703,8 +1615,7 @@ void print_pmu_events(const struct print_callbacks *print_cb, void *print_state) for (j = 0; j < len; j++) { const char *name, *alias = NULL, *scale_unit = NULL, *desc = NULL, *long_desc = NULL, - *encoding_desc = NULL, *topic = NULL, - *metric_name = NULL, *metric_expr = NULL; + *encoding_desc = NULL, *topic = NULL; bool deprecated = false; size_t buf_used; @@ -1742,8 +1653,6 @@ void print_pmu_events(const struct print_callbacks *print_cb, void *print_state) buf_used += snprintf(buf + buf_used, sizeof(buf) - buf_used, "%s/%s/", aliases[j].pmu->name, aliases[j].event->str) + 1; - metric_name = aliases[j].event->metric_name; - metric_expr = aliases[j].event->metric_expr; deprecated = aliases[j].event->deprecated; } print_cb->print_event(print_state, @@ -1756,9 +1665,7 @@ void print_pmu_events(const struct print_callbacks *print_cb, void *print_state) "Kernel PMU event", desc, long_desc, - encoding_desc, - metric_name, - metric_expr); + encoding_desc); } if (printed && pager_in_use()) printf("\n"); @@ -1783,19 +1690,14 @@ bool pmu_have_event(const char *pname, const char *name) return false; } -static FILE *perf_pmu__open_file(struct perf_pmu *pmu, const char *name) +FILE *perf_pmu__open_file(struct perf_pmu *pmu, const char *name) { char path[PATH_MAX]; - const char *sysfs; - sysfs = sysfs__mountpoint(); - if (!sysfs) + if (!perf_pmu__pathname_scnprintf(path, sizeof(path), pmu->name, name) || + !file_available(path)) return NULL; - snprintf(path, PATH_MAX, - "%s" EVENT_SOURCE_DEVICE_PATH "%s/%s", sysfs, pmu->name, name); - if (!file_available(path)) - return NULL; return fopen(path, "r"); } @@ -1816,6 +1718,16 @@ int perf_pmu__scan_file(struct perf_pmu *pmu, const char *name, const char *fmt, return ret; } +bool perf_pmu__file_exists(struct perf_pmu *pmu, const char *name) +{ + char path[PATH_MAX]; + + if (!perf_pmu__pathname_scnprintf(path, sizeof(path), pmu->name, name)) + return false; + + return file_available(path); +} + static int perf_pmu__new_caps(struct list_head *list, char *name, char *value) { struct perf_pmu_caps *caps = zalloc(sizeof(*caps)); @@ -1849,7 +1761,6 @@ int perf_pmu__caps_parse(struct perf_pmu *pmu) { struct stat st; char caps_path[PATH_MAX]; - const char *sysfs = sysfs__mountpoint(); DIR *caps_dir; struct dirent *evt_ent; @@ -1858,12 +1769,9 @@ int perf_pmu__caps_parse(struct perf_pmu *pmu) pmu->nr_caps = 0; - if (!sysfs) + if (!perf_pmu__pathname_scnprintf(caps_path, sizeof(caps_path), pmu->name, "caps")) return -1; - snprintf(caps_path, PATH_MAX, - "%s" EVENT_SOURCE_DEVICE_PATH "%s/caps", sysfs, pmu->name); - if (stat(caps_path, &st) < 0) { pmu->caps_initialized = true; return 0; /* no error if caps does not exist */ @@ -1993,3 +1901,36 @@ int perf_pmu__cpus_match(struct perf_pmu *pmu, struct perf_cpu_map *cpus, *ucpus_ptr = unmatched_cpus; return 0; } + +double __weak perf_pmu__cpu_slots_per_cycle(void) +{ + return NAN; +} + +int perf_pmu__event_source_devices_scnprintf(char *pathname, size_t size) +{ + const char *sysfs = sysfs__mountpoint(); + + if (!sysfs) + return 0; + return scnprintf(pathname, size, "%s/bus/event_source/devices/", sysfs); +} + +/* + * Fill 'buf' with the path to a file or folder in 'pmu_name' in + * sysfs. For example if pmu_name = "cs_etm" and 'filename' = "format" + * then pathname will be filled with + * "/sys/bus/event_source/devices/cs_etm/format" + * + * Return 0 if the sysfs mountpoint couldn't be found or if no + * characters were written. + */ +int perf_pmu__pathname_scnprintf(char *buf, size_t size, + const char *pmu_name, const char *filename) +{ + char base_path[PATH_MAX]; + + if (!perf_pmu__event_source_devices_scnprintf(base_path, sizeof(base_path))) + return 0; + return scnprintf(buf, size, "%s%s/%s", base_path, pmu_name, filename); +} diff --git a/tools/perf/util/pmu.h b/tools/perf/util/pmu.h index 69ca0004f94f..6b770f17eb86 100644 --- a/tools/perf/util/pmu.h +++ b/tools/perf/util/pmu.h @@ -7,6 +7,7 @@ #include <linux/perf_event.h> #include <linux/list.h> #include <stdbool.h> +#include <stdio.h> #include "parse-events.h" #include "pmu-events/pmu-events.h" @@ -22,8 +23,6 @@ enum { }; #define PERF_PMU_FORMAT_BITS 64 -#define EVENT_SOURCE_DEVICE_PATH "/bus/event_source/devices/" -#define CPUS_TEMPLATE_CPU "%s/bus/event_source/devices/%s/cpus" #define MAX_PMU_NAME_LEN 128 struct perf_event_attr; @@ -133,8 +132,6 @@ extern struct perf_pmu perf_pmu__fake; struct perf_pmu_info { const char *unit; - const char *metric_expr; - const char *metric_name; double scale; bool per_pkg; bool snapshot; @@ -188,13 +185,6 @@ struct perf_pmu_alias { * default. */ bool deprecated; - /** - * @metric_expr: A metric expression associated with an event. Doing - * this makes little sense due to scale and unit applying to both. - */ - char *metric_expr; - /** @metric_name: A name for the metric. unit applying to both. */ - char *metric_name; /** @pmu_name: The name copied from struct perf_pmu. */ char *pmu_name; }; @@ -231,6 +221,8 @@ bool pmu_have_event(const char *pname, const char *name); int perf_pmu__scan_file(struct perf_pmu *pmu, const char *name, const char *fmt, ...) __scanf(3, 4); +bool perf_pmu__file_exists(struct perf_pmu *pmu, const char *name); + int perf_pmu__test(void); struct perf_event_attr *perf_pmu__get_default_config(struct perf_pmu *pmu); @@ -239,6 +231,7 @@ void pmu_add_cpu_aliases_table(struct list_head *head, struct perf_pmu *pmu, char *perf_pmu__getcpuid(struct perf_pmu *pmu); const struct pmu_events_table *pmu_events_table__find(void); +const struct pmu_metrics_table *pmu_metrics_table__find(void); bool pmu_uncore_alias_match(const char *pmu_name, const char *name); void perf_pmu_free_alias(struct perf_pmu_alias *alias); @@ -259,4 +252,10 @@ int perf_pmu__cpus_match(struct perf_pmu *pmu, struct perf_cpu_map *cpus, char *pmu_find_real_name(const char *name); char *pmu_find_alias_name(const char *name); +double perf_pmu__cpu_slots_per_cycle(void); +int perf_pmu__event_source_devices_scnprintf(char *pathname, size_t size); +int perf_pmu__pathname_scnprintf(char *buf, size_t size, + const char *pmu_name, const char *filename); +FILE *perf_pmu__open_file(struct perf_pmu *pmu, const char *name); + #endif /* __PMU_H */ diff --git a/tools/perf/util/print-events.c b/tools/perf/util/print-events.c index 2646ae18d9f9..62e9ea7dcf40 100644 --- a/tools/perf/util/print-events.c +++ b/tools/perf/util/print-events.c @@ -101,9 +101,7 @@ void print_tracepoint_events(const struct print_callbacks *print_cb, void *print "Tracepoint event", /*desc=*/NULL, /*long_desc=*/NULL, - /*encoding_desc=*/NULL, - /*metric_name=*/NULL, - /*metric_expr=*/NULL); + /*encoding_desc=*/NULL); } free(dir_path); free(evt_namelist); @@ -195,9 +193,7 @@ void print_sdt_events(const struct print_callbacks *print_cb, void *print_state) "SDT event", /*desc=*/NULL, /*long_desc=*/NULL, - /*encoding_desc=*/NULL, - /*metric_name=*/NULL, - /*metric_expr=*/NULL); + /*encoding_desc=*/NULL); free(evt_name); } @@ -255,9 +251,7 @@ int print_hwcache_events(const struct print_callbacks *print_cb, void *print_sta event_type_descriptors[PERF_TYPE_HW_CACHE], /*desc=*/NULL, /*long_desc=*/NULL, - /*encoding_desc=*/NULL, - /*metric_name=*/NULL, - /*metric_expr=*/NULL); + /*encoding_desc=*/NULL); } strlist__delete(evt_name_list); return 0; @@ -277,9 +271,7 @@ void print_tool_events(const struct print_callbacks *print_cb, void *print_state "Tool event", /*desc=*/NULL, /*long_desc=*/NULL, - /*encoding_desc=*/NULL, - /*metric_name=*/NULL, - /*metric_expr=*/NULL); + /*encoding_desc=*/NULL); } } @@ -331,9 +323,7 @@ void print_symbol_events(const struct print_callbacks *print_cb, void *print_sta event_type_descriptors[type], /*desc=*/NULL, /*long_desc=*/NULL, - /*encoding_desc=*/NULL, - /*metric_name=*/NULL, - /*metric_expr=*/NULL); + /*encoding_desc=*/NULL); } strlist__delete(evt_name_list); } @@ -364,9 +354,7 @@ void print_events(const struct print_callbacks *print_cb, void *print_state) event_type_descriptors[PERF_TYPE_RAW], /*desc=*/NULL, /*long_desc=*/NULL, - /*encoding_desc=*/NULL, - /*metric_name=*/NULL, - /*metric_expr=*/NULL); + /*encoding_desc=*/NULL); print_cb->print_event(print_state, /*topic=*/NULL, @@ -378,9 +366,7 @@ void print_events(const struct print_callbacks *print_cb, void *print_state) event_type_descriptors[PERF_TYPE_RAW], "(see 'man perf-list' on how to encode it)", /*long_desc=*/NULL, - /*encoding_desc=*/NULL, - /*metric_name=*/NULL, - /*metric_expr=*/NULL); + /*encoding_desc=*/NULL); print_cb->print_event(print_state, /*topic=*/NULL, @@ -392,9 +378,7 @@ void print_events(const struct print_callbacks *print_cb, void *print_state) event_type_descriptors[PERF_TYPE_BREAKPOINT], /*desc=*/NULL, /*long_desc=*/NULL, - /*encoding_desc=*/NULL, - /*metric_name=*/NULL, - /*metric_expr=*/NULL); + /*encoding_desc=*/NULL); print_tracepoint_events(print_cb, print_state); diff --git a/tools/perf/util/print-events.h b/tools/perf/util/print-events.h index c237e53c4487..716dcf4b4859 100644 --- a/tools/perf/util/print-events.h +++ b/tools/perf/util/print-events.h @@ -16,8 +16,7 @@ struct print_callbacks { const char *scale_unit, bool deprecated, const char *event_type_desc, const char *desc, const char *long_desc, - const char *encoding_desc, - const char *metric_name, const char *metric_expr); + const char *encoding_desc); void (*print_metric)(void *print_state, const char *group, const char *name, diff --git a/tools/perf/util/probe-event.c b/tools/perf/util/probe-event.c index 0c24bc7afbca..881d94f65a6b 100644 --- a/tools/perf/util/probe-event.c +++ b/tools/perf/util/probe-event.c @@ -917,7 +917,7 @@ static int try_to_find_probe_trace_events(struct perf_probe_event *pev, dinfo = open_debuginfo(pev->target, pev->nsi, !need_dwarf); if (!dinfo) { if (need_dwarf) - return -ENOENT; + return -ENODATA; pr_debug("Could not open debuginfo. Try to use symbols.\n"); return 0; } @@ -956,7 +956,7 @@ static int try_to_find_probe_trace_events(struct perf_probe_event *pev, if (ntevs == 0) { /* No error but failed to find probe point. */ pr_warning("Probe point '%s' not found.\n", synthesize_perf_probe_point(&pev->point)); - return -ENOENT; + return -ENODEV; } else if (ntevs < 0) { /* Error path : ntevs < 0 */ pr_debug("An error occurred in debuginfo analysis (%d).\n", ntevs); diff --git a/tools/perf/util/python.c b/tools/perf/util/python.c index 212031b97910..42e8b813d010 100644 --- a/tools/perf/util/python.c +++ b/tools/perf/util/python.c @@ -20,6 +20,7 @@ #include "stat.h" #include "metricgroup.h" #include "util/env.h" +#include "util/pmu.h" #include <internal/lib.h> #include "util.h" @@ -76,14 +77,7 @@ const char *perf_env__arch(struct perf_env *env __maybe_unused) } /* - * Add this one here not to drag util/stat-shadow.c - */ -void perf_stat__collect_metric_expr(struct evlist *evsel_list) -{ -} - -/* - * This one is needed not to drag the PMU bandwagon, jevents generated + * These ones are needed not to drag the PMU bandwagon, jevents generated * pmu_sys_event_tables, etc and evsel__find_pmu() is used so far just for * doing per PMU perf_event_attr.exclude_guest handling, not really needed, so * far, for the perf python binding known usecases, revisit if this become @@ -94,6 +88,11 @@ struct perf_pmu *evsel__find_pmu(struct evsel *evsel __maybe_unused) return NULL; } +int perf_pmu__scan_file(struct perf_pmu *pmu, const char *name, const char *fmt, ...) +{ + return EOF; +} + /* * Add this one here not to drag util/metricgroup.c */ @@ -442,10 +441,8 @@ tracepoint_field(struct pyrf_event *pe, struct tep_format_field *field) offset = val; len = offset >> 16; offset &= 0xffff; -#ifdef HAVE_LIBTRACEEVENT_TEP_FIELD_IS_RELATIVE - if (field->flags & TEP_FIELD_IS_RELATIVE) + if (tep_field_is_relative(field->flags)) offset += field->offset + field->size; -#endif } if (field->flags & TEP_FIELD_IS_STRING && is_printable_array(data + offset, len)) { diff --git a/tools/perf/util/sample.h b/tools/perf/util/sample.h index 60ec79d4eea4..33b08e0ac746 100644 --- a/tools/perf/util/sample.h +++ b/tools/perf/util/sample.h @@ -92,7 +92,10 @@ struct perf_sample { u8 cpumode; u16 misc; u16 ins_lat; - u16 p_stage_cyc; + union { + u16 p_stage_cyc; + u16 retire_lat; + }; bool no_hw_idx; /* No hw_idx collected in branch_stack */ char insn[MAX_INSN]; void *raw_data; diff --git a/tools/perf/util/scripting-engines/trace-event-perl.c b/tools/perf/util/scripting-engines/trace-event-perl.c index c097b7934fd4..83fd2fd0ba16 100644 --- a/tools/perf/util/scripting-engines/trace-event-perl.c +++ b/tools/perf/util/scripting-engines/trace-event-perl.c @@ -393,10 +393,8 @@ static void perl_process_tracepoint(struct perf_sample *sample, if (field->flags & TEP_FIELD_IS_DYNAMIC) { offset = *(int *)(data + field->offset); offset &= 0xffff; -#ifdef HAVE_LIBTRACEEVENT_TEP_FIELD_IS_RELATIVE - if (field->flags & TEP_FIELD_IS_RELATIVE) + if (tep_field_is_relative(field->flags)) offset += field->offset + field->size; -#endif } else offset = field->offset; XPUSHs(sv_2mortal(newSVpv((char *)data + offset, 0))); diff --git a/tools/perf/util/scripting-engines/trace-event-python.c b/tools/perf/util/scripting-engines/trace-event-python.c index e930f5f1f36d..2c2697c5d025 100644 --- a/tools/perf/util/scripting-engines/trace-event-python.c +++ b/tools/perf/util/scripting-engines/trace-event-python.c @@ -994,10 +994,8 @@ static void python_process_tracepoint(struct perf_sample *sample, offset = val; len = offset >> 16; offset &= 0xffff; -#ifdef HAVE_LIBTRACEEVENT_TEP_FIELD_IS_RELATIVE - if (field->flags & TEP_FIELD_IS_RELATIVE) + if (tep_field_is_relative(field->flags)) offset += field->offset + field->size; -#endif } if (field->flags & TEP_FIELD_IS_STRING && is_printable_array(data + offset, len)) { diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c index 7c021c6cedb9..749d5b5c135b 100644 --- a/tools/perf/util/session.c +++ b/tools/perf/util/session.c @@ -1180,7 +1180,7 @@ static void branch_stack__printf(struct perf_sample *sample, bool callstack) struct branch_entry *e = &entries[i]; if (!callstack) { - printf("..... %2"PRIu64": %016" PRIx64 " -> %016" PRIx64 " %hu cycles %s%s%s%s %x %s\n", + printf("..... %2"PRIu64": %016" PRIx64 " -> %016" PRIx64 " %hu cycles %s%s%s%s %x %s %s\n", i, e->from, e->to, (unsigned short)e->flags.cycles, e->flags.mispred ? "M" : " ", @@ -1188,7 +1188,8 @@ static void branch_stack__printf(struct perf_sample *sample, bool callstack) e->flags.abort ? "A" : " ", e->flags.in_tx ? "T" : " ", (unsigned)e->flags.reserved, - get_branch_type(e)); + get_branch_type(e), + e->flags.spec ? branch_spec_desc(e->flags.spec) : ""); } else { if (i == 0) { printf("..... %2"PRIu64": %016" PRIx64 "\n" @@ -1699,8 +1700,13 @@ static s64 perf_session__process_user_event(struct perf_session *session, case PERF_RECORD_AUXTRACE_INFO: return tool->auxtrace_info(session, event); case PERF_RECORD_AUXTRACE: - /* setup for reading amidst mmap */ - lseek(fd, file_offset + event->header.size, SEEK_SET); + /* + * Setup for reading amidst mmap, but only when we + * are in 'file' mode. The 'pipe' fd is in proper + * place already. + */ + if (!perf_data__is_pipe(session->data)) + lseek(fd, file_offset + event->header.size, SEEK_SET); return tool->auxtrace(session, event); case PERF_RECORD_AUXTRACE_ERROR: perf_session__auxtrace_error_inc(session, event); diff --git a/tools/perf/util/sort.c b/tools/perf/util/sort.c index 37662cdec5ee..093a0c8b2e3d 100644 --- a/tools/perf/util/sort.c +++ b/tools/perf/util/sort.c @@ -28,6 +28,7 @@ #include "time-utils.h" #include "cgroup.h" #include "machine.h" +#include "trace-event.h" #include <linux/kernel.h> #include <linux/string.h> @@ -53,6 +54,13 @@ static const char *const dynamic_headers[] = {"local_ins_lat", "ins_lat", "local static const char *const arch_specific_sort_keys[] = {"local_p_stage_cyc", "p_stage_cyc"}; /* + * Some architectures have Adjacent Cacheline Prefetch feature, which + * behaves like the cacheline size is doubled. Enable this flag to + * check things in double cacheline granularity. + */ +bool chk_double_cl; + +/* * Replaces all occurrences of a char used with the: * * -t, --field-separator @@ -1499,8 +1507,8 @@ sort__dcacheline_cmp(struct hist_entry *left, struct hist_entry *right) addr: /* al_addr does all the right addr - start + offset calculations */ - l = cl_address(left->mem_info->daddr.al_addr); - r = cl_address(right->mem_info->daddr.al_addr); + l = cl_address(left->mem_info->daddr.al_addr, chk_double_cl); + r = cl_address(right->mem_info->daddr.al_addr, chk_double_cl); if (l > r) return -1; if (l < r) return 1; @@ -1519,7 +1527,7 @@ static int hist_entry__dcacheline_snprintf(struct hist_entry *he, char *bf, if (he->mem_info) { struct map *map = he->mem_info->daddr.ms.map; - addr = cl_address(he->mem_info->daddr.al_addr); + addr = cl_address(he->mem_info->daddr.al_addr, chk_double_cl); ms = &he->mem_info->daddr.ms; /* print [s] for shared data mmaps */ @@ -2132,6 +2140,8 @@ static struct sort_dimension common_sort_dimensions[] = { DIM(SORT_LOCAL_PIPELINE_STAGE_CYC, "local_p_stage_cyc", sort_local_p_stage_cyc), DIM(SORT_GLOBAL_PIPELINE_STAGE_CYC, "p_stage_cyc", sort_global_p_stage_cyc), DIM(SORT_ADDR, "addr", sort_addr), + DIM(SORT_LOCAL_RETIRE_LAT, "local_retire_lat", sort_local_p_stage_cyc), + DIM(SORT_GLOBAL_RETIRE_LAT, "retire_lat", sort_global_p_stage_cyc), }; #undef DIM @@ -2667,10 +2677,8 @@ static int64_t __sort__hde_cmp(struct perf_hpp_fmt *fmt, tep_read_number_field(field, a->raw_data, &dyn); offset = dyn & 0xffff; size = (dyn >> 16) & 0xffff; -#ifdef HAVE_LIBTRACEEVENT_TEP_FIELD_IS_RELATIVE - if (field->flags & TEP_FIELD_IS_RELATIVE) + if (tep_field_is_relative(field->flags)) offset += field->offset + field->size; -#endif /* record max width for output */ if (size > hde->dynamic_len) hde->dynamic_len = size; diff --git a/tools/perf/util/sort.h b/tools/perf/util/sort.h index 921715e6aec4..d79a100e5999 100644 --- a/tools/perf/util/sort.h +++ b/tools/perf/util/sort.h @@ -35,6 +35,7 @@ extern struct sort_entry sort_sym_from; extern struct sort_entry sort_sym_to; extern struct sort_entry sort_srcline; extern const char default_mem_sort_order[]; +extern bool chk_double_cl; struct res_sample { u64 time; @@ -237,6 +238,8 @@ enum sort_type { SORT_LOCAL_PIPELINE_STAGE_CYC, SORT_GLOBAL_PIPELINE_STAGE_CYC, SORT_ADDR, + SORT_LOCAL_RETIRE_LAT, + SORT_GLOBAL_RETIRE_LAT, /* branch stack specific sort keys */ __SORT_BRANCH_STACK, diff --git a/tools/perf/util/stat-display.c b/tools/perf/util/stat-display.c index 8bd8b0142630..1b5cb20efd23 100644 --- a/tools/perf/util/stat-display.c +++ b/tools/perf/util/stat-display.c @@ -787,6 +787,51 @@ static void uniquify_counter(struct perf_stat_config *config, struct evsel *coun uniquify_event_name(counter); } +/** + * should_skip_zero_count() - Check if the event should print 0 values. + * @config: The perf stat configuration (including aggregation mode). + * @counter: The evsel with its associated cpumap. + * @id: The aggregation id that is being queried. + * + * Due to mismatch between the event cpumap or thread-map and the + * aggregation mode, sometimes it'd iterate the counter with the map + * which does not contain any values. + * + * For example, uncore events have dedicated CPUs to manage them, + * result for other CPUs should be zero and skipped. + * + * Return: %true if the value should NOT be printed, %false if the value + * needs to be printed like "<not counted>" or "<not supported>". + */ +static bool should_skip_zero_counter(struct perf_stat_config *config, + struct evsel *counter, + const struct aggr_cpu_id *id) +{ + struct perf_cpu cpu; + int idx; + + /* + * Skip value 0 when enabling --per-thread globally, + * otherwise it will have too many 0 output. + */ + if (config->aggr_mode == AGGR_THREAD && config->system_wide) + return true; + /* + * Skip value 0 when it's an uncore event and the given aggr id + * does not belong to the PMU cpumask. + */ + if (!counter->pmu || !counter->pmu->is_uncore) + return false; + + perf_cpu_map__for_each_cpu(cpu, idx, counter->pmu->cpus) { + struct aggr_cpu_id own_id = config->aggr_get_id(config, cpu); + + if (aggr_cpu_id__equal(id, &own_id)) + return false; + } + return true; +} + static void print_counter_aggrdata(struct perf_stat_config *config, struct evsel *counter, int s, struct outstate *os) @@ -814,11 +859,7 @@ static void print_counter_aggrdata(struct perf_stat_config *config, ena = aggr->counts.ena; run = aggr->counts.run; - /* - * Skip value 0 when enabling --per-thread globally, otherwise it will - * have too many 0 output. - */ - if (val == 0 && config->aggr_mode == AGGR_THREAD && config->system_wide) + if (val == 0 && should_skip_zero_counter(config, counter, &id)) return; if (!metric_only) { diff --git a/tools/perf/util/stat-shadow.c b/tools/perf/util/stat-shadow.c index cadb2df23c87..806b32156459 100644 --- a/tools/perf/util/stat-shadow.c +++ b/tools/perf/util/stat-shadow.c @@ -311,7 +311,7 @@ void perf_stat__update_shadow_stats(struct evsel *counter, u64 count, update_stats(&v->stats, count); if (counter->metric_leader) v->metric_total += count; - } else if (counter->metric_leader) { + } else if (counter->metric_leader && !counter->merged_stat) { v = saved_value_lookup(counter->metric_leader, map_idx, true, STAT_NONE, 0, st, rsd.cgrp); v->metric_total += count; @@ -346,114 +346,6 @@ static const char *get_ratio_color(enum grc_type type, double ratio) return color; } -static struct evsel *perf_stat__find_event(struct evlist *evsel_list, - const char *name) -{ - struct evsel *c2; - - evlist__for_each_entry (evsel_list, c2) { - if (!strcasecmp(c2->name, name) && !c2->collect_stat) - return c2; - } - return NULL; -} - -/* Mark MetricExpr target events and link events using them to them. */ -void perf_stat__collect_metric_expr(struct evlist *evsel_list) -{ - struct evsel *counter, *leader, **metric_events, *oc; - bool found; - struct expr_parse_ctx *ctx; - struct hashmap_entry *cur; - size_t bkt; - int i; - - ctx = expr__ctx_new(); - if (!ctx) { - pr_debug("expr__ctx_new failed"); - return; - } - evlist__for_each_entry(evsel_list, counter) { - bool invalid = false; - - leader = evsel__leader(counter); - if (!counter->metric_expr) - continue; - - expr__ctx_clear(ctx); - metric_events = counter->metric_events; - if (!metric_events) { - if (expr__find_ids(counter->metric_expr, - counter->name, - ctx) < 0) - continue; - - metric_events = calloc(sizeof(struct evsel *), - hashmap__size(ctx->ids) + 1); - if (!metric_events) { - expr__ctx_free(ctx); - return; - } - counter->metric_events = metric_events; - } - - i = 0; - hashmap__for_each_entry(ctx->ids, cur, bkt) { - const char *metric_name = cur->pkey; - - found = false; - if (leader) { - /* Search in group */ - for_each_group_member (oc, leader) { - if (!strcasecmp(oc->name, - metric_name) && - !oc->collect_stat) { - found = true; - break; - } - } - } - if (!found) { - /* Search ignoring groups */ - oc = perf_stat__find_event(evsel_list, - metric_name); - } - if (!oc) { - /* Deduping one is good enough to handle duplicated PMUs. */ - static char *printed; - - /* - * Adding events automatically would be difficult, because - * it would risk creating groups that are not schedulable. - * perf stat doesn't understand all the scheduling constraints - * of events. So we ask the user instead to add the missing - * events. - */ - if (!printed || - strcasecmp(printed, metric_name)) { - fprintf(stderr, - "Add %s event to groups to get metric expression for %s\n", - metric_name, - counter->name); - free(printed); - printed = strdup(metric_name); - } - invalid = true; - continue; - } - metric_events[i++] = oc; - oc->collect_stat = true; - } - metric_events[i] = NULL; - if (invalid) { - free(metric_events); - counter->metric_events = NULL; - counter->metric_expr = NULL; - } - } - expr__ctx_free(ctx); -} - static double runtime_stat_avg(struct runtime_stat *st, enum stat_type type, int map_idx, struct runtime_stat_data *rsd) @@ -1299,10 +1191,6 @@ void perf_stat__print_shadow_stats(struct perf_stat_config *config, color = NULL; print_metric(config, ctxp, color, "%8.1f%%", "Core Bound", core_bound * 100.); - } else if (evsel->metric_expr) { - generic_metric(config, evsel->metric_expr, evsel->metric_events, NULL, - evsel->name, evsel->metric_name, NULL, 1, - map_idx, out, st); } else if (runtime_stat_n(st, STAT_NSECS, map_idx, &rsd) != 0) { char unit = ' '; char unit_buf[10] = "/sec"; diff --git a/tools/perf/util/stat.c b/tools/perf/util/stat.c index 534d36d26fc3..a07473703c6d 100644 --- a/tools/perf/util/stat.c +++ b/tools/perf/util/stat.c @@ -842,11 +842,7 @@ int create_perf_stat_counter(struct evsel *evsel, if (evsel__is_group_leader(evsel)) { attr->disabled = 1; - /* - * In case of initial_delay we enable tracee - * events manually. - */ - if (target__none(target) && !config->initial_delay) + if (target__enable_on_exec(target)) attr->enable_on_exec = 1; } diff --git a/tools/perf/util/stat.h b/tools/perf/util/stat.h index 499c3bf81333..bf1794ebc916 100644 --- a/tools/perf/util/stat.h +++ b/tools/perf/util/stat.h @@ -166,7 +166,6 @@ struct perf_stat_config { FILE *output; unsigned int interval; unsigned int timeout; - int initial_delay; unsigned int unit_width; unsigned int metric_only_len; int times; @@ -257,7 +256,6 @@ void perf_stat__print_shadow_stats(struct perf_stat_config *config, struct perf_stat_output_ctx *out, struct rblist *metric_events, struct runtime_stat *st); -void perf_stat__collect_metric_expr(struct evlist *); int evlist__alloc_stats(struct perf_stat_config *config, struct evlist *evlist, bool alloc_raw); diff --git a/tools/perf/util/symbol-elf.c b/tools/perf/util/symbol-elf.c index 96767d1b3f1c..41882ae8452e 100644 --- a/tools/perf/util/symbol-elf.c +++ b/tools/perf/util/symbol-elf.c @@ -323,15 +323,325 @@ static char *demangle_sym(struct dso *dso, int kmodule, const char *elf_name) return demangled; } -#define elf_section__for_each_rel(reldata, pos, pos_mem, idx, nr_entries) \ - for (idx = 0, pos = gelf_getrel(reldata, 0, &pos_mem); \ - idx < nr_entries; \ - ++idx, pos = gelf_getrel(reldata, idx, &pos_mem)) +struct rel_info { + u32 nr_entries; + u32 *sorted; + bool is_rela; + Elf_Data *reldata; + GElf_Rela rela; + GElf_Rel rel; +}; + +static u32 get_rel_symidx(struct rel_info *ri, u32 idx) +{ + idx = ri->sorted ? ri->sorted[idx] : idx; + if (ri->is_rela) { + gelf_getrela(ri->reldata, idx, &ri->rela); + return GELF_R_SYM(ri->rela.r_info); + } + gelf_getrel(ri->reldata, idx, &ri->rel); + return GELF_R_SYM(ri->rel.r_info); +} -#define elf_section__for_each_rela(reldata, pos, pos_mem, idx, nr_entries) \ - for (idx = 0, pos = gelf_getrela(reldata, 0, &pos_mem); \ - idx < nr_entries; \ - ++idx, pos = gelf_getrela(reldata, idx, &pos_mem)) +static u64 get_rel_offset(struct rel_info *ri, u32 x) +{ + if (ri->is_rela) { + GElf_Rela rela; + + gelf_getrela(ri->reldata, x, &rela); + return rela.r_offset; + } else { + GElf_Rel rel; + + gelf_getrel(ri->reldata, x, &rel); + return rel.r_offset; + } +} + +static int rel_cmp(const void *a, const void *b, void *r) +{ + struct rel_info *ri = r; + u64 a_offset = get_rel_offset(ri, *(const u32 *)a); + u64 b_offset = get_rel_offset(ri, *(const u32 *)b); + + return a_offset < b_offset ? -1 : (a_offset > b_offset ? 1 : 0); +} + +static int sort_rel(struct rel_info *ri) +{ + size_t sz = sizeof(ri->sorted[0]); + u32 i; + + ri->sorted = calloc(ri->nr_entries, sz); + if (!ri->sorted) + return -1; + for (i = 0; i < ri->nr_entries; i++) + ri->sorted[i] = i; + qsort_r(ri->sorted, ri->nr_entries, sz, rel_cmp, ri); + return 0; +} + +/* + * For x86_64, the GNU linker is putting IFUNC information in the relocation + * addend. + */ +static bool addend_may_be_ifunc(GElf_Ehdr *ehdr, struct rel_info *ri) +{ + return ehdr->e_machine == EM_X86_64 && ri->is_rela && + GELF_R_TYPE(ri->rela.r_info) == R_X86_64_IRELATIVE; +} + +static bool get_ifunc_name(Elf *elf, struct dso *dso, GElf_Ehdr *ehdr, + struct rel_info *ri, char *buf, size_t buf_sz) +{ + u64 addr = ri->rela.r_addend; + struct symbol *sym; + GElf_Phdr phdr; + + if (!addend_may_be_ifunc(ehdr, ri)) + return false; + + if (elf_read_program_header(elf, addr, &phdr)) + return false; + + addr -= phdr.p_vaddr - phdr.p_offset; + + sym = dso__find_symbol_nocache(dso, addr); + + /* Expecting the address to be an IFUNC or IFUNC alias */ + if (!sym || sym->start != addr || (sym->type != STT_GNU_IFUNC && !sym->ifunc_alias)) + return false; + + snprintf(buf, buf_sz, "%s@plt", sym->name); + + return true; +} + +static void exit_rel(struct rel_info *ri) +{ + free(ri->sorted); +} + +static bool get_plt_sizes(struct dso *dso, GElf_Ehdr *ehdr, GElf_Shdr *shdr_plt, + u64 *plt_header_size, u64 *plt_entry_size) +{ + switch (ehdr->e_machine) { + case EM_ARM: + *plt_header_size = 20; + *plt_entry_size = 12; + return true; + case EM_AARCH64: + *plt_header_size = 32; + *plt_entry_size = 16; + return true; + case EM_SPARC: + *plt_header_size = 48; + *plt_entry_size = 12; + return true; + case EM_SPARCV9: + *plt_header_size = 128; + *plt_entry_size = 32; + return true; + case EM_386: + case EM_X86_64: + *plt_entry_size = shdr_plt->sh_entsize; + /* Size is 8 or 16, if not, assume alignment indicates size */ + if (*plt_entry_size != 8 && *plt_entry_size != 16) + *plt_entry_size = shdr_plt->sh_addralign == 8 ? 8 : 16; + *plt_header_size = *plt_entry_size; + break; + default: /* FIXME: s390/alpha/mips/parisc/poperpc/sh/xtensa need to be checked */ + *plt_header_size = shdr_plt->sh_entsize; + *plt_entry_size = shdr_plt->sh_entsize; + break; + } + if (*plt_entry_size) + return true; + pr_debug("Missing PLT entry size for %s\n", dso->long_name); + return false; +} + +static bool machine_is_x86(GElf_Half e_machine) +{ + return e_machine == EM_386 || e_machine == EM_X86_64; +} + +struct rela_dyn { + GElf_Addr offset; + u32 sym_idx; +}; + +struct rela_dyn_info { + struct dso *dso; + Elf_Data *plt_got_data; + u32 nr_entries; + struct rela_dyn *sorted; + Elf_Data *dynsym_data; + Elf_Data *dynstr_data; + Elf_Data *rela_dyn_data; +}; + +static void exit_rela_dyn(struct rela_dyn_info *di) +{ + free(di->sorted); +} + +static int cmp_offset(const void *a, const void *b) +{ + const struct rela_dyn *va = a; + const struct rela_dyn *vb = b; + + return va->offset < vb->offset ? -1 : (va->offset > vb->offset ? 1 : 0); +} + +static int sort_rela_dyn(struct rela_dyn_info *di) +{ + u32 i, n; + + di->sorted = calloc(di->nr_entries, sizeof(di->sorted[0])); + if (!di->sorted) + return -1; + + /* Get data for sorting: the offset and symbol index */ + for (i = 0, n = 0; i < di->nr_entries; i++) { + GElf_Rela rela; + u32 sym_idx; + + gelf_getrela(di->rela_dyn_data, i, &rela); + sym_idx = GELF_R_SYM(rela.r_info); + if (sym_idx) { + di->sorted[n].sym_idx = sym_idx; + di->sorted[n].offset = rela.r_offset; + n += 1; + } + } + + /* Sort by offset */ + di->nr_entries = n; + qsort(di->sorted, n, sizeof(di->sorted[0]), cmp_offset); + + return 0; +} + +static void get_rela_dyn_info(Elf *elf, GElf_Ehdr *ehdr, struct rela_dyn_info *di, Elf_Scn *scn) +{ + GElf_Shdr rela_dyn_shdr; + GElf_Shdr shdr; + + di->plt_got_data = elf_getdata(scn, NULL); + + scn = elf_section_by_name(elf, ehdr, &rela_dyn_shdr, ".rela.dyn", NULL); + if (!scn || !rela_dyn_shdr.sh_link || !rela_dyn_shdr.sh_entsize) + return; + + di->nr_entries = rela_dyn_shdr.sh_size / rela_dyn_shdr.sh_entsize; + di->rela_dyn_data = elf_getdata(scn, NULL); + + scn = elf_getscn(elf, rela_dyn_shdr.sh_link); + if (!scn || !gelf_getshdr(scn, &shdr) || !shdr.sh_link) + return; + + di->dynsym_data = elf_getdata(scn, NULL); + di->dynstr_data = elf_getdata(elf_getscn(elf, shdr.sh_link), NULL); + + if (!di->plt_got_data || !di->dynstr_data || !di->dynsym_data || !di->rela_dyn_data) + return; + + /* Sort into offset order */ + sort_rela_dyn(di); +} + +/* Get instruction displacement from a plt entry for x86_64 */ +static u32 get_x86_64_plt_disp(const u8 *p) +{ + u8 endbr64[] = {0xf3, 0x0f, 0x1e, 0xfa}; + int n = 0; + + /* Skip endbr64 */ + if (!memcmp(p, endbr64, sizeof(endbr64))) + n += sizeof(endbr64); + /* Skip bnd prefix */ + if (p[n] == 0xf2) + n += 1; + /* jmp with 4-byte displacement */ + if (p[n] == 0xff && p[n + 1] == 0x25) { + n += 2; + /* Also add offset from start of entry to end of instruction */ + return n + 4 + le32toh(*(const u32 *)(p + n)); + } + return 0; +} + +static bool get_plt_got_name(GElf_Shdr *shdr, size_t i, + struct rela_dyn_info *di, + char *buf, size_t buf_sz) +{ + struct rela_dyn vi, *vr; + const char *sym_name; + char *demangled; + GElf_Sym sym; + u32 disp; + + if (!di->sorted) + return false; + + disp = get_x86_64_plt_disp(di->plt_got_data->d_buf + i); + if (!disp) + return false; + + /* Compute target offset of the .plt.got entry */ + vi.offset = shdr->sh_offset + di->plt_got_data->d_off + i + disp; + + /* Find that offset in .rela.dyn (sorted by offset) */ + vr = bsearch(&vi, di->sorted, di->nr_entries, sizeof(di->sorted[0]), cmp_offset); + if (!vr) + return false; + + /* Get the associated symbol */ + gelf_getsym(di->dynsym_data, vr->sym_idx, &sym); + sym_name = elf_sym__name(&sym, di->dynstr_data); + demangled = demangle_sym(di->dso, 0, sym_name); + if (demangled != NULL) + sym_name = demangled; + + snprintf(buf, buf_sz, "%s@plt", sym_name); + + free(demangled); + + return *sym_name; +} + +static int dso__synthesize_plt_got_symbols(struct dso *dso, Elf *elf, + GElf_Ehdr *ehdr, + char *buf, size_t buf_sz) +{ + struct rela_dyn_info di = { .dso = dso }; + struct symbol *sym; + GElf_Shdr shdr; + Elf_Scn *scn; + int err = -1; + size_t i; + + scn = elf_section_by_name(elf, ehdr, &shdr, ".plt.got", NULL); + if (!scn || !shdr.sh_entsize) + return 0; + + if (ehdr->e_machine == EM_X86_64) + get_rela_dyn_info(elf, ehdr, &di, scn); + + for (i = 0; i < shdr.sh_size; i += shdr.sh_entsize) { + if (!get_plt_got_name(&shdr, i, &di, buf, buf_sz)) + snprintf(buf, buf_sz, "offset_%#" PRIx64 "@plt", (u64)shdr.sh_offset + i); + sym = symbol__new(shdr.sh_offset + i, shdr.sh_entsize, STB_GLOBAL, STT_FUNC, buf); + if (!sym) + goto out; + symbols__insert(&dso->symbols, sym); + } + err = 0; +out: + exit_rela_dyn(&di); + return err; +} /* * We need to check if we have a .dynsym, so that we can handle the @@ -342,56 +652,104 @@ static char *demangle_sym(struct dso *dso, int kmodule, const char *elf_name) */ int dso__synthesize_plt_symbols(struct dso *dso, struct symsrc *ss) { - uint32_t nr_rel_entries, idx; + uint32_t idx; GElf_Sym sym; u64 plt_offset, plt_header_size, plt_entry_size; - GElf_Shdr shdr_plt; - struct symbol *f; + GElf_Shdr shdr_plt, plt_sec_shdr; + struct symbol *f, *plt_sym; GElf_Shdr shdr_rel_plt, shdr_dynsym; - Elf_Data *reldata, *syms, *symstrs; + Elf_Data *syms, *symstrs; Elf_Scn *scn_plt_rel, *scn_symstrs, *scn_dynsym; - size_t dynsym_idx; GElf_Ehdr ehdr; char sympltname[1024]; Elf *elf; - int nr = 0, symidx, err = 0; - - if (!ss->dynsym) - return 0; + int nr = 0, err = -1; + struct rel_info ri = { .is_rela = false }; + bool lazy_plt; elf = ss->elf; ehdr = ss->ehdr; - scn_dynsym = ss->dynsym; - shdr_dynsym = ss->dynshdr; - dynsym_idx = ss->dynsym_idx; + if (!elf_section_by_name(elf, &ehdr, &shdr_plt, ".plt", NULL)) + return 0; + + /* + * A symbol from a previous section (e.g. .init) can have been expanded + * by symbols__fixup_end() to overlap .plt. Truncate it before adding + * a symbol for .plt header. + */ + f = dso__find_symbol_nocache(dso, shdr_plt.sh_offset); + if (f && f->start < shdr_plt.sh_offset && f->end > shdr_plt.sh_offset) + f->end = shdr_plt.sh_offset; + + if (!get_plt_sizes(dso, &ehdr, &shdr_plt, &plt_header_size, &plt_entry_size)) + return 0; + + /* Add a symbol for .plt header */ + plt_sym = symbol__new(shdr_plt.sh_offset, plt_header_size, STB_GLOBAL, STT_FUNC, ".plt"); + if (!plt_sym) + goto out_elf_end; + symbols__insert(&dso->symbols, plt_sym); - if (scn_dynsym == NULL) + /* Only x86 has .plt.got */ + if (machine_is_x86(ehdr.e_machine) && + dso__synthesize_plt_got_symbols(dso, elf, &ehdr, sympltname, sizeof(sympltname))) goto out_elf_end; + /* Only x86 has .plt.sec */ + if (machine_is_x86(ehdr.e_machine) && + elf_section_by_name(elf, &ehdr, &plt_sec_shdr, ".plt.sec", NULL)) { + if (!get_plt_sizes(dso, &ehdr, &plt_sec_shdr, &plt_header_size, &plt_entry_size)) + return 0; + /* Extend .plt symbol to entire .plt */ + plt_sym->end = plt_sym->start + shdr_plt.sh_size; + /* Use .plt.sec offset */ + plt_offset = plt_sec_shdr.sh_offset; + lazy_plt = false; + } else { + plt_offset = shdr_plt.sh_offset; + lazy_plt = true; + } + scn_plt_rel = elf_section_by_name(elf, &ehdr, &shdr_rel_plt, ".rela.plt", NULL); if (scn_plt_rel == NULL) { scn_plt_rel = elf_section_by_name(elf, &ehdr, &shdr_rel_plt, ".rel.plt", NULL); if (scn_plt_rel == NULL) - goto out_elf_end; + return 0; } - err = -1; + if (shdr_rel_plt.sh_type != SHT_RELA && + shdr_rel_plt.sh_type != SHT_REL) + return 0; - if (shdr_rel_plt.sh_link != dynsym_idx) - goto out_elf_end; + if (!shdr_rel_plt.sh_link) + return 0; - if (elf_section_by_name(elf, &ehdr, &shdr_plt, ".plt", NULL) == NULL) + if (shdr_rel_plt.sh_link == ss->dynsym_idx) { + scn_dynsym = ss->dynsym; + shdr_dynsym = ss->dynshdr; + } else if (shdr_rel_plt.sh_link == ss->symtab_idx) { + /* + * A static executable can have a .plt due to IFUNCs, in which + * case .symtab is used not .dynsym. + */ + scn_dynsym = ss->symtab; + shdr_dynsym = ss->symshdr; + } else { goto out_elf_end; + } + + if (!scn_dynsym) + return 0; /* * Fetch the relocation section to find the idxes to the GOT * and the symbols in the .dynsym they refer to. */ - reldata = elf_getdata(scn_plt_rel, NULL); - if (reldata == NULL) + ri.reldata = elf_getdata(scn_plt_rel, NULL); + if (!ri.reldata) goto out_elf_end; syms = elf_getdata(scn_dynsym, NULL); @@ -409,93 +767,57 @@ int dso__synthesize_plt_symbols(struct dso *dso, struct symsrc *ss) if (symstrs->d_size == 0) goto out_elf_end; - nr_rel_entries = shdr_rel_plt.sh_size / shdr_rel_plt.sh_entsize; - plt_offset = shdr_plt.sh_offset; - switch (ehdr.e_machine) { - case EM_ARM: - plt_header_size = 20; - plt_entry_size = 12; - break; + ri.nr_entries = shdr_rel_plt.sh_size / shdr_rel_plt.sh_entsize; - case EM_AARCH64: - plt_header_size = 32; - plt_entry_size = 16; - break; + ri.is_rela = shdr_rel_plt.sh_type == SHT_RELA; - case EM_SPARC: - plt_header_size = 48; - plt_entry_size = 12; - break; - - case EM_SPARCV9: - plt_header_size = 128; - plt_entry_size = 32; - break; - - default: /* FIXME: s390/alpha/mips/parisc/poperpc/sh/xtensa need to be checked */ - plt_header_size = shdr_plt.sh_entsize; - plt_entry_size = shdr_plt.sh_entsize; - break; + if (lazy_plt) { + /* + * Assume a .plt with the same number of entries as the number + * of relocation entries is not lazy and does not have a header. + */ + if (ri.nr_entries * plt_entry_size == shdr_plt.sh_size) + dso__delete_symbol(dso, plt_sym); + else + plt_offset += plt_header_size; } - plt_offset += plt_header_size; - - if (shdr_rel_plt.sh_type == SHT_RELA) { - GElf_Rela pos_mem, *pos; - elf_section__for_each_rela(reldata, pos, pos_mem, idx, - nr_rel_entries) { - const char *elf_name = NULL; - char *demangled = NULL; - symidx = GELF_R_SYM(pos->r_info); - gelf_getsym(syms, symidx, &sym); + /* + * x86 doesn't insert IFUNC relocations in .plt order, so sort to get + * back in order. + */ + if (machine_is_x86(ehdr.e_machine) && sort_rel(&ri)) + goto out_elf_end; - elf_name = elf_sym__name(&sym, symstrs); - demangled = demangle_sym(dso, 0, elf_name); - if (demangled != NULL) - elf_name = demangled; - snprintf(sympltname, sizeof(sympltname), - "%s@plt", elf_name); - free(demangled); + for (idx = 0; idx < ri.nr_entries; idx++) { + const char *elf_name = NULL; + char *demangled = NULL; - f = symbol__new(plt_offset, plt_entry_size, - STB_GLOBAL, STT_FUNC, sympltname); - if (!f) - goto out_elf_end; + gelf_getsym(syms, get_rel_symidx(&ri, idx), &sym); - plt_offset += plt_entry_size; - symbols__insert(&dso->symbols, f); - ++nr; - } - } else if (shdr_rel_plt.sh_type == SHT_REL) { - GElf_Rel pos_mem, *pos; - elf_section__for_each_rel(reldata, pos, pos_mem, idx, - nr_rel_entries) { - const char *elf_name = NULL; - char *demangled = NULL; - symidx = GELF_R_SYM(pos->r_info); - gelf_getsym(syms, symidx, &sym); - - elf_name = elf_sym__name(&sym, symstrs); - demangled = demangle_sym(dso, 0, elf_name); - if (demangled != NULL) - elf_name = demangled; + elf_name = elf_sym__name(&sym, symstrs); + demangled = demangle_sym(dso, 0, elf_name); + if (demangled) + elf_name = demangled; + if (*elf_name) + snprintf(sympltname, sizeof(sympltname), "%s@plt", elf_name); + else if (!get_ifunc_name(elf, dso, &ehdr, &ri, sympltname, sizeof(sympltname))) snprintf(sympltname, sizeof(sympltname), - "%s@plt", elf_name); - free(demangled); + "offset_%#" PRIx64 "@plt", plt_offset); + free(demangled); - f = symbol__new(plt_offset, plt_entry_size, - STB_GLOBAL, STT_FUNC, sympltname); - if (!f) - goto out_elf_end; + f = symbol__new(plt_offset, plt_entry_size, STB_GLOBAL, STT_FUNC, sympltname); + if (!f) + goto out_elf_end; - plt_offset += plt_entry_size; - symbols__insert(&dso->symbols, f); - ++nr; - } + plt_offset += plt_entry_size; + symbols__insert(&dso->symbols, f); + ++nr; } err = 0; out_elf_end: + exit_rel(&ri); if (err == 0) return nr; pr_debug("%s: problems reading %s PLT info.\n", @@ -946,8 +1268,9 @@ int symsrc__init(struct symsrc *ss, struct dso *dso, const char *name, ss->is_64_bit = (gelf_getclass(elf) == ELFCLASS64); + ss->symtab_idx = 0; ss->symtab = elf_section_by_name(elf, &ehdr, &ss->symshdr, ".symtab", - NULL); + &ss->symtab_idx); if (ss->symshdr.sh_type != SHT_SYMTAB) ss->symtab = NULL; diff --git a/tools/perf/util/symbol.c b/tools/perf/util/symbol.c index a3a165ae933a..317c0706852f 100644 --- a/tools/perf/util/symbol.c +++ b/tools/perf/util/symbol.c @@ -201,10 +201,14 @@ again: continue; if (choose_best_symbol(curr, next) == SYMBOL_A) { + if (next->type == STT_GNU_IFUNC) + curr->ifunc_alias = true; rb_erase_cached(&next->rb_node, symbols); symbol__delete(next); goto again; } else { + if (curr->type == STT_GNU_IFUNC) + next->ifunc_alias = true; nd = rb_next(&curr->rb_node); rb_erase_cached(&curr->rb_node, symbols); symbol__delete(curr); @@ -556,6 +560,11 @@ struct symbol *dso__find_symbol(struct dso *dso, u64 addr) return dso->last_find_result.symbol; } +struct symbol *dso__find_symbol_nocache(struct dso *dso, u64 addr) +{ + return symbols__find(&dso->symbols, addr); +} + struct symbol *dso__first_symbol(struct dso *dso) { return symbols__first(&dso->symbols); @@ -1368,10 +1377,23 @@ static int dso__load_kcore(struct dso *dso, struct map *map, /* Find the kernel map using the '_stext' symbol */ if (!kallsyms__get_function_start(kallsyms_filename, "_stext", &stext)) { + u64 replacement_size = 0; + list_for_each_entry(new_map, &md.maps, node) { - if (stext >= new_map->start && stext < new_map->end) { + u64 new_size = new_map->end - new_map->start; + + if (!(stext >= new_map->start && stext < new_map->end)) + continue; + + /* + * On some architectures, ARM64 for example, the kernel + * text can get allocated inside of the vmalloc segment. + * Select the smallest matching segment, in case stext + * falls within more than one in the list. + */ + if (!replacement_map || new_size < replacement_size) { replacement_map = new_map; - break; + replacement_size = new_size; } } } diff --git a/tools/perf/util/symbol.h b/tools/perf/util/symbol.h index f735108c4d4e..7558735543c2 100644 --- a/tools/perf/util/symbol.h +++ b/tools/perf/util/symbol.h @@ -64,6 +64,8 @@ struct symbol { u8 inlined:1; /** Has symbol__annotate2 been performed. */ u8 annotate2:1; + /** Symbol is an alias of an STT_GNU_IFUNC */ + u8 ifunc_alias:1; /** Architecture specific. Unused except on PPC where it holds st_other. */ u8 arch_sym; /** The name of length namelen associated with the symbol. */ @@ -148,6 +150,7 @@ void dso__delete_symbol(struct dso *dso, struct symbol *sym); struct symbol *dso__find_symbol(struct dso *dso, u64 addr); +struct symbol *dso__find_symbol_nocache(struct dso *dso, u64 addr); struct symbol *dso__find_symbol_by_name(struct dso *dso, const char *name); struct symbol *symbol__next_by_name(struct symbol *sym); diff --git a/tools/perf/util/symsrc.h b/tools/perf/util/symsrc.h index 2665b4bde751..edf82028c9e6 100644 --- a/tools/perf/util/symsrc.h +++ b/tools/perf/util/symsrc.h @@ -26,6 +26,7 @@ struct symsrc { GElf_Shdr opdshdr; Elf_Scn *symtab; + size_t symtab_idx; GElf_Shdr symshdr; Elf_Scn *dynsym; diff --git a/tools/perf/util/synthetic-events.c b/tools/perf/util/synthetic-events.c index 3ab6a92b1a6d..9ab9308ee80c 100644 --- a/tools/perf/util/synthetic-events.c +++ b/tools/perf/util/synthetic-events.c @@ -2219,8 +2219,8 @@ int perf_event__synthesize_build_id(struct perf_tool *tool, struct dso *pos, u16 len = pos->long_name_len + 1; len = PERF_ALIGN(len, NAME_ALIGN); - memcpy(&ev.build_id.build_id, pos->bid.data, sizeof(pos->bid.data)); - ev.build_id.size = pos->bid.size; + ev.build_id.size = min(pos->bid.size, sizeof(pos->bid.data)); + memcpy(&ev.build_id.build_id, pos->bid.data, ev.build_id.size); ev.build_id.header.type = PERF_RECORD_HEADER_BUILD_ID; ev.build_id.header.misc = misc | PERF_RECORD_MISC_BUILD_ID_SIZE; ev.build_id.pid = machine->pid; diff --git a/tools/perf/util/target.h b/tools/perf/util/target.h index daec6cba500d..880f1af7f6ad 100644 --- a/tools/perf/util/target.h +++ b/tools/perf/util/target.h @@ -18,6 +18,7 @@ struct target { bool per_thread; bool use_bpf; bool hybrid; + int initial_delay; const char *attr_map; }; @@ -72,6 +73,17 @@ static inline bool target__none(struct target *target) return !target__has_task(target) && !target__has_cpu(target); } +static inline bool target__enable_on_exec(struct target *target) +{ + /* + * Normally enable_on_exec should be set if: + * 1) The tracee process is forked (not attaching to existed task or cpu). + * 2) And initial_delay is not configured. + * Otherwise, we enable tracee events manually. + */ + return target__none(target) && !target->initial_delay; +} + static inline bool target__has_per_thread(struct target *target) { return target->system_wide && target->per_thread; diff --git a/tools/perf/util/trace-event.h b/tools/perf/util/trace-event.h index 9b3cd79cca12..a0cff184b1cd 100644 --- a/tools/perf/util/trace-event.h +++ b/tools/perf/util/trace-event.h @@ -21,6 +21,9 @@ struct trace_event { struct tep_plugin_list *plugin_list; }; +/* Computes a version number comparable with LIBTRACEEVENT_VERSION from Makefile.config. */ +#define MAKE_LIBTRACEEVENT_VERSION(a, b, c) ((a)*255*255+(b)*255+(c)) + typedef char *(tep_func_resolver_t)(void *priv, unsigned long long *addrp, char **modp); @@ -137,4 +140,20 @@ int common_lock_depth(struct scripting_context *context); #define SAMPLE_FLAGS_BUF_SIZE 64 int perf_sample__sprintf_flags(u32 flags, char *str, size_t sz); +#if defined(LIBTRACEEVENT_VERSION) && LIBTRACEEVENT_VERSION >= MAKE_LIBTRACEEVENT_VERSION(1, 5, 0) +#include <traceevent/event-parse.h> + +static inline bool tep_field_is_relative(unsigned long flags) +{ + return (flags & TEP_FIELD_IS_RELATIVE) != 0; +} +#else +#include <linux/compiler.h> + +static inline bool tep_field_is_relative(unsigned long flags __maybe_unused) +{ + return false; +} +#endif + #endif /* _PERF_UTIL_TRACE_EVENT_H */ |